From 1470de327339678b04c4029e3326b32fa78e0a5e Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Fri, 31 Oct 2025 14:31:56 +0100 Subject: [PATCH] chore(rails): refactor and fix test app setup --- sentry-rails/.rspec | 2 +- sentry-rails/spec/dummy/test_rails_app/app.rb | 123 ------------- .../app/controllers/hello_controller.rb | 41 +++++ .../app/controllers/posts_controller.rb | 32 ++++ .../test_rails_app/app/models/comment.rb | 5 + .../dummy/test_rails_app/app/models/post.rb | 6 + .../{ => app/views}/test_template.html.erb | 0 .../spec/dummy/test_rails_app/apps/5-2.rb | 113 ------------ .../spec/dummy/test_rails_app/apps/6-0.rb | 113 ------------ .../spec/dummy/test_rails_app/apps/6-1.rb | 114 ------------ .../spec/dummy/test_rails_app/apps/7-0.rb | 114 ------------ .../spec/dummy/test_rails_app/apps/7-1.rb | 114 ------------ .../spec/dummy/test_rails_app/config.ru | 0 .../test_rails_app/config/application.rb | 162 ++++++++++++++++++ .../config/applications/rails-5.2.rb | 6 + .../config/applications/rails-6.0.rb | 18 ++ .../config/applications/rails-6.1.rb | 12 ++ .../config/applications/rails-7.0.rb | 33 ++++ .../config/applications/rails-latest.rb | 49 ++++++ .../dummy/test_rails_app/config/database.yml | 6 - .../dummy/test_rails_app/config/storage.yml | 1 - .../spec/dummy/test_rails_app/configs/5-2.rb | 9 - .../spec/dummy/test_rails_app/configs/6-0.rb | 15 -- .../spec/dummy/test_rails_app/configs/6-1.rb | 13 -- .../spec/dummy/test_rails_app/configs/7-0.rb | 32 ---- .../spec/dummy/test_rails_app/configs/7-1.rb | 42 ----- .../spec/dummy/test_rails_app/configs/7-2.rb | 42 ----- .../spec/dummy/test_rails_app/db/schema.rb | 41 +++++ .../spec/isolated/rails_logger_patch_spec.rb | 153 ++++++++--------- .../spec/sentry/rails/activejob_spec.rb | 81 +++++---- sentry-rails/spec/sentry/rails/client_spec.rb | 22 ++- .../active_record_subscriber_spec.rb | 2 +- .../tracing/active_record_subscriber_spec.rb | 4 +- .../tracing/active_storage_subscriber_spec.rb | 36 ++-- sentry-rails/spec/sentry/rails_spec.rb | 83 +++++---- sentry-rails/spec/spec_helper.rb | 54 +++--- sentry-rails/spec/support/test_helper.rb | 32 ++++ .../spec/versioned/2.7/activejob_spec.rb | 2 +- sentry-ruby.code-workspace | 3 +- 39 files changed, 668 insertions(+), 1062 deletions(-) delete mode 100644 sentry-rails/spec/dummy/test_rails_app/app.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/app/controllers/hello_controller.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/app/controllers/posts_controller.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/app/models/comment.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/app/models/post.rb rename sentry-rails/spec/dummy/test_rails_app/{ => app/views}/test_template.html.erb (100%) delete mode 100644 sentry-rails/spec/dummy/test_rails_app/apps/5-2.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/apps/6-0.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/apps/6-1.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/apps/7-0.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/apps/7-1.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/config.ru create mode 100644 sentry-rails/spec/dummy/test_rails_app/config/application.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/config/applications/rails-5.2.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/config/applications/rails-6.0.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/config/applications/rails-6.1.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/config/applications/rails-7.0.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/config/applications/rails-latest.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/config/database.yml delete mode 100644 sentry-rails/spec/dummy/test_rails_app/configs/5-2.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/configs/6-0.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/configs/6-1.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/configs/7-0.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/configs/7-1.rb delete mode 100644 sentry-rails/spec/dummy/test_rails_app/configs/7-2.rb create mode 100644 sentry-rails/spec/dummy/test_rails_app/db/schema.rb create mode 100644 sentry-rails/spec/support/test_helper.rb diff --git a/sentry-rails/.rspec b/sentry-rails/.rspec index 34c5164d9..bd5e02887 100644 --- a/sentry-rails/.rspec +++ b/sentry-rails/.rspec @@ -1,3 +1,3 @@ ---format documentation +--format progress --color --require spec_helper diff --git a/sentry-rails/spec/dummy/test_rails_app/app.rb b/sentry-rails/spec/dummy/test_rails_app/app.rb deleted file mode 100644 index 1522bb73d..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/app.rb +++ /dev/null @@ -1,123 +0,0 @@ -# frozen_string_literal: true - -ENV["RAILS_ENV"] = "test" - -require "rails" - -require "active_record" -require "active_job/railtie" -require "action_view/railtie" -require "action_controller/railtie" - -require 'sentry/rails' - -ActiveRecord::Base.logger = Logger.new(nil) -ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "db") - -class TestApp < Rails::Application -end - -v5_2 = Gem::Version.new("5.2") -v6_0 = Gem::Version.new("6.0") -v6_1 = Gem::Version.new("6.1") -v7_0 = Gem::Version.new("7.0") -v7_1 = Gem::Version.new("7.1") - -FILE_NAME = - case Gem::Version.new(Rails.version) - when ->(v) { v.between?(v5_2, v6_0) } - "5-2" - when ->(v) { v.between?(v6_0, v6_1) } - "6-0" - when ->(v) { v.between?(v6_1, v7_0) } - "6-1" - when ->(v) { v > v7_0 && v < v7_1 } - "7-0" - when ->(v) { v >= v7_1 } - "7-1" - end - -# require files and defined relevant setup methods for the Rails version -require "dummy/test_rails_app/configs/#{FILE_NAME}" - -def make_basic_app(&block) - run_pre_initialize_cleanup - - app = Class.new(TestApp) do - def self.name - "RailsTestApp" - end - end - - app.config.active_support.deprecation = :silence - app.config.action_controller.view_paths = "spec/dummy/test_rails_app" - app.config.hosts = nil - app.config.secret_key_base = "test" - app.config.sdk_logger = ActiveSupport::Logger.new(nil) - app.config.eager_load = false - app.config.active_job.queue_adapter = :test - app.config.cache_store = :memory_store - app.config.action_controller.perform_caching = true - - # Eager load namespaces can be accumulated after repeated initializations and make initialization - # slower after each run - # This is especially obvious in Rails 7.2, because of https://github.com/rails/rails/pull/49987, but other constants's - # accumulation can also cause slowdown - # Because this is not necessary for the test, we can simply clear it here - app.config.eager_load_namespaces.clear - - configure_app(app) - - # Configure parameter filtering for consistent test behavior - app.config.filter_parameters = [ - :password, - :secret, - :custom_secret, - :api_key, - :credit_card, - :authorization, - :token - ] - - app.routes.append do - get "/exception", to: "hello#exception" - get "/view_exception", to: "hello#view_exception" - get "/view", to: "hello#view" - get "/not_found", to: "hello#not_found" - get "/world", to: "hello#world" - get "/with_custom_instrumentation", to: "hello#with_custom_instrumentation" - resources :posts, only: [:index, :show] do - member do - get :attach - end - end - get "500", to: "hello#reporting" - root to: "hello#world" - end - - app.initializer :configure_sentry do - Sentry.init do |config| - config.release = 'beta' - config.dsn = "http://12345:67890@sentry.localdomain:3000/sentry/42" - config.transport.transport_class = Sentry::DummyTransport - # for sending events synchronously - config.background_worker_threads = 0 - config.include_local_variables = true - yield(config, app) if block_given? - end - end - - app.initialize! - - Rails.application = app - - # load application code for the Rails version - require "dummy/test_rails_app/apps/#{FILE_NAME}" - - Post.all.to_a # to run the sqlte version query first - - # and then clear breadcrumbs in case the above query is recorded - Sentry.get_current_scope.clear_breadcrumbs if Sentry.initialized? - - app -end diff --git a/sentry-rails/spec/dummy/test_rails_app/app/controllers/hello_controller.rb b/sentry-rails/spec/dummy/test_rails_app/app/controllers/hello_controller.rb new file mode 100644 index 000000000..284a16053 --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/app/controllers/hello_controller.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class HelloController < ActionController::Base + protect_from_forgery with: :exception + + def exception + raise "An unhandled exception!" + end + + def reporting + render plain: Sentry.last_event_id + end + + def view_exception + render inline: "<%= foo %>" + end + + def view + render template: "test_template" + end + + def world + render plain: "Hello World!" + end + + def with_custom_instrumentation + custom_event = "custom.instrument" + ActiveSupport::Notifications.subscribe(custom_event) do |*args| + data = args[-1] + data += 1 + end + + ActiveSupport::Notifications.instrument(custom_event, 1) + + head :ok + end + + def not_found + raise ActionController::BadRequest + end +end diff --git a/sentry-rails/spec/dummy/test_rails_app/app/controllers/posts_controller.rb b/sentry-rails/spec/dummy/test_rails_app/app/controllers/posts_controller.rb new file mode 100644 index 000000000..c4d4ad055 --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/app/controllers/posts_controller.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class PostsController < ActionController::Base + def index + Post.all.to_a + raise "foo" + end + + def show + p = Post.find(params[:id]) + + render plain: p.id + end + + def attach + p = Post.find(params[:id]) + + attach_params = { + io: File.open(File.join(Rails.root, "public", "sentry-logo.png")), + filename: "sentry-logo.png" + } + + # service_name parameter was added in Rails 6.1 + if Rails.gem_version >= Gem::Version.new("6.1.0") + attach_params[:service_name] = "test" + end + + p.cover.attach(attach_params) + + render plain: p.id + end +end diff --git a/sentry-rails/spec/dummy/test_rails_app/app/models/comment.rb b/sentry-rails/spec/dummy/test_rails_app/app/models/comment.rb new file mode 100644 index 000000000..5b323d4ff --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/app/models/comment.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Comment < ActiveRecord::Base + belongs_to :post +end diff --git a/sentry-rails/spec/dummy/test_rails_app/app/models/post.rb b/sentry-rails/spec/dummy/test_rails_app/app/models/post.rb new file mode 100644 index 000000000..9609fc041 --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/app/models/post.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Post < ActiveRecord::Base + has_many :comments + has_one_attached :cover +end diff --git a/sentry-rails/spec/dummy/test_rails_app/test_template.html.erb b/sentry-rails/spec/dummy/test_rails_app/app/views/test_template.html.erb similarity index 100% rename from sentry-rails/spec/dummy/test_rails_app/test_template.html.erb rename to sentry-rails/spec/dummy/test_rails_app/app/views/test_template.html.erb diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/5-2.rb b/sentry-rails/spec/dummy/test_rails_app/apps/5-2.rb deleted file mode 100644 index 273698df3..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/apps/5-2.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -ActiveRecord::Schema.define do - create_table "active_storage_attachments", force: :cascade do |t| - t.string "name", null: false - t.string "record_type", null: false - t.integer "record_id", null: false - t.integer "blob_id", null: false - t.datetime "created_at", null: false - t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" - t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true - end - - create_table "active_storage_blobs", force: :cascade do |t| - t.string "key", null: false - t.string "filename", null: false - t.string "content_type" - t.text "metadata" - t.string "service_name" - t.bigint "byte_size", null: false - t.string "checksum", null: false - t.datetime "created_at", null: false - t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true - end - - create_table "active_storage_variant_records", force: :cascade do |t| - t.integer "blob_id", null: false - t.string "variation_digest", null: false - t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true - end - - create_table :posts, force: true do |t| - t.string :title - t.timestamps - end - - create_table :comments, force: true do |t| - t.integer :post_id - end -end - -class Post < ActiveRecord::Base - has_many :comments - has_one_attached :cover -end - -class Comment < ActiveRecord::Base - belongs_to :post -end - -class PostsController < ActionController::Base - def index - Post.all.to_a - raise "foo" - end - - def show - p = Post.find(params[:id]) - - render plain: p.id - end - - def attach - p = Post.find(params[:id]) - - attach_params = { - io: File.open(File.join(Rails.root, 'public', 'sentry-logo.png')), - filename: 'sentry-logo.png' - } - - p.cover.attach(attach_params) - - render plain: p.id - end -end - -class HelloController < ActionController::Base - def exception - raise "An unhandled exception!" - end - - def reporting - render plain: Sentry.last_event_id - end - - def view_exception - render inline: "<%= foo %>" - end - - def view - render template: "test_template" - end - - def world - render plain: "Hello World!" - end - - def with_custom_instrumentation - custom_event = "custom.instrument" - ActiveSupport::Notifications.subscribe(custom_event) do |*args| - data = args[-1] - data += 1 - end - - ActiveSupport::Notifications.instrument(custom_event, 1) - - head :ok - end - - def not_found - raise ActionController::BadRequest - end -end diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/6-0.rb b/sentry-rails/spec/dummy/test_rails_app/apps/6-0.rb deleted file mode 100644 index 273698df3..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/apps/6-0.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -ActiveRecord::Schema.define do - create_table "active_storage_attachments", force: :cascade do |t| - t.string "name", null: false - t.string "record_type", null: false - t.integer "record_id", null: false - t.integer "blob_id", null: false - t.datetime "created_at", null: false - t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" - t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true - end - - create_table "active_storage_blobs", force: :cascade do |t| - t.string "key", null: false - t.string "filename", null: false - t.string "content_type" - t.text "metadata" - t.string "service_name" - t.bigint "byte_size", null: false - t.string "checksum", null: false - t.datetime "created_at", null: false - t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true - end - - create_table "active_storage_variant_records", force: :cascade do |t| - t.integer "blob_id", null: false - t.string "variation_digest", null: false - t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true - end - - create_table :posts, force: true do |t| - t.string :title - t.timestamps - end - - create_table :comments, force: true do |t| - t.integer :post_id - end -end - -class Post < ActiveRecord::Base - has_many :comments - has_one_attached :cover -end - -class Comment < ActiveRecord::Base - belongs_to :post -end - -class PostsController < ActionController::Base - def index - Post.all.to_a - raise "foo" - end - - def show - p = Post.find(params[:id]) - - render plain: p.id - end - - def attach - p = Post.find(params[:id]) - - attach_params = { - io: File.open(File.join(Rails.root, 'public', 'sentry-logo.png')), - filename: 'sentry-logo.png' - } - - p.cover.attach(attach_params) - - render plain: p.id - end -end - -class HelloController < ActionController::Base - def exception - raise "An unhandled exception!" - end - - def reporting - render plain: Sentry.last_event_id - end - - def view_exception - render inline: "<%= foo %>" - end - - def view - render template: "test_template" - end - - def world - render plain: "Hello World!" - end - - def with_custom_instrumentation - custom_event = "custom.instrument" - ActiveSupport::Notifications.subscribe(custom_event) do |*args| - data = args[-1] - data += 1 - end - - ActiveSupport::Notifications.instrument(custom_event, 1) - - head :ok - end - - def not_found - raise ActionController::BadRequest - end -end diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/6-1.rb b/sentry-rails/spec/dummy/test_rails_app/apps/6-1.rb deleted file mode 100644 index 494fe0eeb..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/apps/6-1.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -ActiveRecord::Schema.define do - create_table "active_storage_attachments", force: :cascade do |t| - t.string "name", null: false - t.string "record_type", null: false - t.integer "record_id", null: false - t.integer "blob_id", null: false - t.datetime "created_at", null: false - t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" - t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true - end - - create_table "active_storage_blobs", force: :cascade do |t| - t.string "key", null: false - t.string "filename", null: false - t.string "content_type" - t.text "metadata" - t.string "service_name" - t.bigint "byte_size", null: false - t.string "checksum", null: false - t.datetime "created_at", null: false - t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true - end - - create_table "active_storage_variant_records", force: :cascade do |t| - t.integer "blob_id", null: false - t.string "variation_digest", null: false - t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true - end - - create_table :posts, force: true do |t| - t.string :title - t.timestamps - end - - create_table :comments, force: true do |t| - t.integer :post_id - end -end - -class Post < ActiveRecord::Base - has_many :comments - has_one_attached :cover -end - -class Comment < ActiveRecord::Base - belongs_to :post -end - -class PostsController < ActionController::Base - def index - Post.all.to_a - raise "foo" - end - - def show - p = Post.find(params[:id]) - - render plain: p.id - end - - def attach - p = Post.find(params[:id]) - - attach_params = { - io: File.open(File.join(Rails.root, 'public', 'sentry-logo.png')), - filename: 'sentry-logo.png', - service_name: "test" - } - - p.cover.attach(attach_params) - - render plain: p.id - end -end - -class HelloController < ActionController::Base - def exception - raise "An unhandled exception!" - end - - def reporting - render plain: Sentry.last_event_id - end - - def view_exception - render inline: "<%= foo %>" - end - - def view - render template: "test_template" - end - - def world - render plain: "Hello World!" - end - - def with_custom_instrumentation - custom_event = "custom.instrument" - ActiveSupport::Notifications.subscribe(custom_event) do |*args| - data = args[-1] - data += 1 - end - - ActiveSupport::Notifications.instrument(custom_event, 1) - - head :ok - end - - def not_found - raise ActionController::BadRequest - end -end diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/7-0.rb b/sentry-rails/spec/dummy/test_rails_app/apps/7-0.rb deleted file mode 100644 index 494fe0eeb..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/apps/7-0.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -ActiveRecord::Schema.define do - create_table "active_storage_attachments", force: :cascade do |t| - t.string "name", null: false - t.string "record_type", null: false - t.integer "record_id", null: false - t.integer "blob_id", null: false - t.datetime "created_at", null: false - t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" - t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true - end - - create_table "active_storage_blobs", force: :cascade do |t| - t.string "key", null: false - t.string "filename", null: false - t.string "content_type" - t.text "metadata" - t.string "service_name" - t.bigint "byte_size", null: false - t.string "checksum", null: false - t.datetime "created_at", null: false - t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true - end - - create_table "active_storage_variant_records", force: :cascade do |t| - t.integer "blob_id", null: false - t.string "variation_digest", null: false - t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true - end - - create_table :posts, force: true do |t| - t.string :title - t.timestamps - end - - create_table :comments, force: true do |t| - t.integer :post_id - end -end - -class Post < ActiveRecord::Base - has_many :comments - has_one_attached :cover -end - -class Comment < ActiveRecord::Base - belongs_to :post -end - -class PostsController < ActionController::Base - def index - Post.all.to_a - raise "foo" - end - - def show - p = Post.find(params[:id]) - - render plain: p.id - end - - def attach - p = Post.find(params[:id]) - - attach_params = { - io: File.open(File.join(Rails.root, 'public', 'sentry-logo.png')), - filename: 'sentry-logo.png', - service_name: "test" - } - - p.cover.attach(attach_params) - - render plain: p.id - end -end - -class HelloController < ActionController::Base - def exception - raise "An unhandled exception!" - end - - def reporting - render plain: Sentry.last_event_id - end - - def view_exception - render inline: "<%= foo %>" - end - - def view - render template: "test_template" - end - - def world - render plain: "Hello World!" - end - - def with_custom_instrumentation - custom_event = "custom.instrument" - ActiveSupport::Notifications.subscribe(custom_event) do |*args| - data = args[-1] - data += 1 - end - - ActiveSupport::Notifications.instrument(custom_event, 1) - - head :ok - end - - def not_found - raise ActionController::BadRequest - end -end diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/7-1.rb b/sentry-rails/spec/dummy/test_rails_app/apps/7-1.rb deleted file mode 100644 index 494fe0eeb..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/apps/7-1.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -ActiveRecord::Schema.define do - create_table "active_storage_attachments", force: :cascade do |t| - t.string "name", null: false - t.string "record_type", null: false - t.integer "record_id", null: false - t.integer "blob_id", null: false - t.datetime "created_at", null: false - t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" - t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true - end - - create_table "active_storage_blobs", force: :cascade do |t| - t.string "key", null: false - t.string "filename", null: false - t.string "content_type" - t.text "metadata" - t.string "service_name" - t.bigint "byte_size", null: false - t.string "checksum", null: false - t.datetime "created_at", null: false - t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true - end - - create_table "active_storage_variant_records", force: :cascade do |t| - t.integer "blob_id", null: false - t.string "variation_digest", null: false - t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true - end - - create_table :posts, force: true do |t| - t.string :title - t.timestamps - end - - create_table :comments, force: true do |t| - t.integer :post_id - end -end - -class Post < ActiveRecord::Base - has_many :comments - has_one_attached :cover -end - -class Comment < ActiveRecord::Base - belongs_to :post -end - -class PostsController < ActionController::Base - def index - Post.all.to_a - raise "foo" - end - - def show - p = Post.find(params[:id]) - - render plain: p.id - end - - def attach - p = Post.find(params[:id]) - - attach_params = { - io: File.open(File.join(Rails.root, 'public', 'sentry-logo.png')), - filename: 'sentry-logo.png', - service_name: "test" - } - - p.cover.attach(attach_params) - - render plain: p.id - end -end - -class HelloController < ActionController::Base - def exception - raise "An unhandled exception!" - end - - def reporting - render plain: Sentry.last_event_id - end - - def view_exception - render inline: "<%= foo %>" - end - - def view - render template: "test_template" - end - - def world - render plain: "Hello World!" - end - - def with_custom_instrumentation - custom_event = "custom.instrument" - ActiveSupport::Notifications.subscribe(custom_event) do |*args| - data = args[-1] - data += 1 - end - - ActiveSupport::Notifications.instrument(custom_event, 1) - - head :ok - end - - def not_found - raise ActionController::BadRequest - end -end diff --git a/sentry-rails/spec/dummy/test_rails_app/config.ru b/sentry-rails/spec/dummy/test_rails_app/config.ru deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry-rails/spec/dummy/test_rails_app/config/application.rb b/sentry-rails/spec/dummy/test_rails_app/config/application.rb new file mode 100644 index 000000000..02a5756e7 --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/config/application.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +require "active_record" + +require "active_job/railtie" +require "action_view/railtie" +require "action_controller/railtie" + +require "action_cable/engine" +require "active_storage/engine" + +ActiveRecord::Base.logger = Logger.new(nil) + +module Sentry + module Rails + module Test + class Application < ::Rails::Application + def self.define + klass = Class.new(Sentry::TestRailsApp) + + klass.define_singleton_method(:name) { + "Sentry::TestRailsApp_#{klass.version}::Anonymous#{object_id}" + } + + klass.configure + + yield(klass) if block_given? + + klass.before_initialize! + klass.initialize! + klass.after_initialize! + + ::Rails.application = klass + + klass + end + + def self.version + @version ||= ::Rails.version.to_f.to_s + end + + def self.root_path + @root_path ||= Pathname(__dir__).join("..").expand_path + end + + def self.schema_file + @schema_file ||= root_path.join("db/schema.rb") + end + + def self.db_path + @db_path ||= root_path.join("db", "db.sqlite3") + end + + def self.application_file + @application_file ||= begin + current = Dir[root_path.join("config/applications/rails-*.rb")] + .map { |f| File.basename(f, ".rb").split("-").last } + .find { |f| f == version } + + "rails-#{current || "latest"}" + end + end + + def self.load_test_schema + @__schema_loaded__ ||= begin + # This is more reliable than setting config/database.yml + ENV["DATABASE_URL"] = "sqlite3://#{db_path}" + + # Silence migrations output + ActiveRecord::Migration.verbose = false + + # We need to connect manually here + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: db_path) + + # Load schema from db/schema.rb into the current connection + require Test::Application.schema_file + + true + end + end + + # Configure method that sets up base configuration + # This can be inherited and extended by subclasses + def configure + config.root = Test::Application.root_path + config.logger = ActiveSupport::Logger.new(nil) + config.active_support.deprecation = :silence + config.hosts = nil + config.secret_key_base = "test 123" + config.sdk_logger = ActiveSupport::Logger.new(nil) + config.eager_load = true + config.active_job.queue_adapter = :test + config.cache_store = :memory_store + config.action_controller.perform_caching = true + config.active_storage.service = :test + + config.filter_parameters = [ + :password, + :secret, + :custom_secret, + :api_key, + :credit_card, + :authorization, + :token + ] + + # Eager load namespaces can be accumulated after repeated initializations and make initialization + # slower after each run + # This is especially obvious in Rails 7.2, because of https://github.com/rails/rails/pull/49987, but other constants's + # accumulation can also cause slowdown + # Because this is not necessary for the test, we can simply clear it here + config.eager_load_namespaces.clear + + routes.append do + get "/exception", to: "hello#exception" + get "/view_exception", to: "hello#view_exception" + get "/view", to: "hello#view" + get "/not_found", to: "hello#not_found" + get "/world", to: "hello#world" + get "/with_custom_instrumentation", to: "hello#with_custom_instrumentation" + + resources :posts, only: [:index, :show] do + member do + get :attach + end + end + + get "500", to: "hello#reporting" + + root to: "hello#world" + end + end + + def before_initialize! + # no-op by default + end + + def after_initialize! + if Sentry.initialized? + # Run a query to make sure the schema metadata gets loaded and cached + Post.all.to_a.inspect + + # Clear breadcrumbs to avoid pollution from the query during test runs + Sentry.get_current_scope.clear_breadcrumbs + end + end + + def cleanup! + ::Rails.application = nil + end + + require_relative "applications/#{application_file}" + + ::Rails.module_eval do + def self.root + Sentry::TestRailsApp.root_path + end + end + end + end + end +end diff --git a/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-5.2.rb b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-5.2.rb new file mode 100644 index 000000000..fa4a03482 --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-5.2.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Sentry + class TestRailsApp < Sentry::Rails::Test::Application + end +end diff --git a/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-6.0.rb b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-6.0.rb new file mode 100644 index 000000000..097f7a72b --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-6.0.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Sentry + class TestRailsApp < Sentry::Rails::Test::Application + def configure + super + config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new + config.active_record.sqlite3.represent_boolean_as_integer = nil + end + + def before_initialize! + ActionCable::Channel::Base.reset_callbacks(:subscribe) + ActionCable::Channel::Base.reset_callbacks(:unsubscribe) + + super + end + end +end diff --git a/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-6.1.rb b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-6.1.rb new file mode 100644 index 000000000..7c432a8db --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-6.1.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Sentry + class TestRailsApp < Sentry::Rails::Test::Application + def cleanup! + ActionCable::Channel::Base.reset_callbacks(:subscribe) + ActionCable::Channel::Base.reset_callbacks(:unsubscribe) + + super + end + end +end diff --git a/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-7.0.rb b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-7.0.rb new file mode 100644 index 000000000..910fdc1fe --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-7.0.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Sentry + class TestRailsApp < Sentry::Rails::Test::Application + def cleanup! + # Zeitwerk checks if registered loaders load paths repeatedly and raises error if that happens. + # And because every new Rails::Application instance registers its own loader, we need to clear + # previously registered ones from Zeitwerk. + Zeitwerk::Registry.loaders.clear + + # Rails removes the support of multiple instances, which includes freezing some setting values. + ActiveSupport::Dependencies.autoload_once_paths = [] + ActiveSupport::Dependencies.autoload_paths = [] + + # there are a few Rails initializers/finializers that register hook to the executor + # because the callbacks are stored inside the `ActiveSupport::Executor` class instead of an + # instance the callbacks duplicate after each time we initialize the application and cause + # issues when they're executed + ActiveSupport::Executor.reset_callbacks(:run) + ActiveSupport::Executor.reset_callbacks(:complete) + + # Rails uses this module to set a global context for its ErrorReporter feature. + # this needs to be cleared so previously set context won't pollute later reportings + # see ErrorSubscriber. + ActiveSupport::ExecutionContext.clear + + ActionCable::Channel::Base.reset_callbacks(:subscribe) + ActionCable::Channel::Base.reset_callbacks(:unsubscribe) + + super + end + end +end diff --git a/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-latest.rb b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-latest.rb new file mode 100644 index 000000000..ce6c512d2 --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/config/applications/rails-latest.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "sentry/rails/error_subscriber" + +module Sentry + class TestRailsApp < Sentry::Rails::Test::Application + def configure + super + config.enable_reloading = false + end + + def cleanup! + # Zeitwerk checks if registered loaders load paths repeatedly and raises error if that happens. + # And because every new Rails::Application instance registers its own loader, we need to clear + # previously registered ones from Zeitwerk. + Zeitwerk::Registry.loaders.clear + + # Rails removes the support of multiple instances, which includes freezing some setting values. + ActiveSupport::Dependencies.autoload_once_paths = [] + ActiveSupport::Dependencies.autoload_paths = [] + + # there are a few Rails initializers/finializers that register hook to the executor + # because the callbacks are stored inside the `ActiveSupport::Executor` class instead of an + # instance the callbacks duplicate after each time we initialize the application and cause + # issues when they're executed + ActiveSupport::Executor.reset_callbacks(:run) + ActiveSupport::Executor.reset_callbacks(:complete) + + # Rails uses this module to set a global context for its ErrorReporter feature. + # this needs to be cleared so previously set context won't pollute later reportings + # see ErrorSubscriber. + ActiveSupport::ExecutionContext.clear + + ActionCable::Channel::Base.reset_callbacks(:subscribe) + ActionCable::Channel::Base.reset_callbacks(:unsubscribe) + + # Rails 7.1 stores the error reporter directly under the ActiveSupport class. + # So we need to make sure the subscriber is not subscribed unexpectedly before any tests + ActiveSupport.error_reporter.unsubscribe(Sentry::Rails::ErrorSubscriber) + + # This is 8.1+ only + if ActiveSupport.respond_to?(:filter_parameters) + ActiveSupport.filter_parameters.clear + end + + super + end + end +end diff --git a/sentry-rails/spec/dummy/test_rails_app/config/database.yml b/sentry-rails/spec/dummy/test_rails_app/config/database.yml deleted file mode 100644 index edd919ddc..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/config/database.yml +++ /dev/null @@ -1,6 +0,0 @@ -default: &default - adapter: sqlite3 - -test: - <<: *default - database: db diff --git a/sentry-rails/spec/dummy/test_rails_app/config/storage.yml b/sentry-rails/spec/dummy/test_rails_app/config/storage.yml index c7061bb25..6861af3b7 100644 --- a/sentry-rails/spec/dummy/test_rails_app/config/storage.yml +++ b/sentry-rails/spec/dummy/test_rails_app/config/storage.yml @@ -1,4 +1,3 @@ test: service: Disk root: <%= Rails.root.join("tmp/storage") %> - diff --git a/sentry-rails/spec/dummy/test_rails_app/configs/5-2.rb b/sentry-rails/spec/dummy/test_rails_app/configs/5-2.rb deleted file mode 100644 index d5d88d8b6..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/configs/5-2.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -require "active_storage/engine" - -def run_pre_initialize_cleanup; end - -def configure_app(app) - app.config.active_storage.service = :test -end diff --git a/sentry-rails/spec/dummy/test_rails_app/configs/6-0.rb b/sentry-rails/spec/dummy/test_rails_app/configs/6-0.rb deleted file mode 100644 index bab5746a8..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/configs/6-0.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "active_storage/engine" -require "action_cable/engine" - -def run_pre_initialize_cleanup - ActionCable::Channel::Base.reset_callbacks(:subscribe) - ActionCable::Channel::Base.reset_callbacks(:unsubscribe) -end - -def configure_app(app) - app.config.active_storage.service = :test - app.config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new - app.config.active_record.sqlite3.represent_boolean_as_integer = nil -end diff --git a/sentry-rails/spec/dummy/test_rails_app/configs/6-1.rb b/sentry-rails/spec/dummy/test_rails_app/configs/6-1.rb deleted file mode 100644 index e36c83344..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/configs/6-1.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require "active_storage/engine" -require "action_cable/engine" - -def run_pre_initialize_cleanup - ActionCable::Channel::Base.reset_callbacks(:subscribe) - ActionCable::Channel::Base.reset_callbacks(:unsubscribe) -end - -def configure_app(app) - app.config.active_storage.service = :test -end diff --git a/sentry-rails/spec/dummy/test_rails_app/configs/7-0.rb b/sentry-rails/spec/dummy/test_rails_app/configs/7-0.rb deleted file mode 100644 index 84652ae04..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/configs/7-0.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require "active_storage/engine" -require "action_cable/engine" - -def run_pre_initialize_cleanup - # Zeitwerk checks if registered loaders load paths repeatedly and raises error if that happens. - # And because every new Rails::Application instance registers its own loader, we need to clear previously registered ones from Zeitwerk. - Zeitwerk::Registry.loaders.clear - - # Rails removes the support of multiple instances, which includes freezing some setting values. - # This is the workaround to avoid FrozenError. Related issue: https://github.com/rails/rails/issues/42319 - ActiveSupport::Dependencies.autoload_once_paths = [] - ActiveSupport::Dependencies.autoload_paths = [] - - # there are a few Rails initializers/finializers that register hook to the executor - # because the callbacks are stored inside the `ActiveSupport::Executor` class instead of an instance - # the callbacks duplicate after each time we initialize the application and cause issues when they're executed - ActiveSupport::Executor.reset_callbacks(:run) - ActiveSupport::Executor.reset_callbacks(:complete) - - # Rails uses this module to set a global context for its ErrorReporter feature. - # this needs to be cleared so previously set context won't pollute later reportings (see ErrorSubscriber). - ActiveSupport::ExecutionContext.clear - - ActionCable::Channel::Base.reset_callbacks(:subscribe) - ActionCable::Channel::Base.reset_callbacks(:unsubscribe) -end - -def configure_app(app) - app.config.active_storage.service = :test -end diff --git a/sentry-rails/spec/dummy/test_rails_app/configs/7-1.rb b/sentry-rails/spec/dummy/test_rails_app/configs/7-1.rb deleted file mode 100644 index 4a0a79580..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/configs/7-1.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require "active_storage/engine" -require "action_cable/engine" -require "sentry/rails/error_subscriber" - -def run_pre_initialize_cleanup - # Zeitwerk checks if registered loaders load paths repeatedly and raises error if that happens. - # And because every new Rails::Application instance registers its own loader, we need to clear previously registered ones from Zeitwerk. - Zeitwerk::Registry.loaders.clear - - # Rails removes the support of multiple instances, which includes freezing some setting values. - # This is the workaround to avoid FrozenError. Related issue: https://github.com/rails/rails/issues/42319 - ActiveSupport::Dependencies.autoload_once_paths = [] - ActiveSupport::Dependencies.autoload_paths = [] - - # there are a few Rails initializers/finializers that register hook to the executor - # because the callbacks are stored inside the `ActiveSupport::Executor` class instead of an instance - # the callbacks duplicate after each time we initialize the application and cause issues when they're executed - ActiveSupport::Executor.reset_callbacks(:run) - ActiveSupport::Executor.reset_callbacks(:complete) - - # Rails uses this module to set a global context for its ErrorReporter feature. - # this needs to be cleared so previously set context won't pollute later reportings (see ErrorSubscriber). - ActiveSupport::ExecutionContext.clear - - ActionCable::Channel::Base.reset_callbacks(:subscribe) - ActionCable::Channel::Base.reset_callbacks(:unsubscribe) - - # Rails 7.1 stores the error reporter directly under the ActiveSupport class. - # So we need to make sure the subscriber is not subscribed unexpectedly before any tests - ActiveSupport.error_reporter.unsubscribe(Sentry::Rails::ErrorSubscriber) - - if ActiveSupport.respond_to?(:filter_parameters) - ActiveSupport.filter_parameters.clear - end -end - -def configure_app(app) - app.config.active_storage.service = :test - app.config.enable_reloading = false -end diff --git a/sentry-rails/spec/dummy/test_rails_app/configs/7-2.rb b/sentry-rails/spec/dummy/test_rails_app/configs/7-2.rb deleted file mode 100644 index 4a0a79580..000000000 --- a/sentry-rails/spec/dummy/test_rails_app/configs/7-2.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require "active_storage/engine" -require "action_cable/engine" -require "sentry/rails/error_subscriber" - -def run_pre_initialize_cleanup - # Zeitwerk checks if registered loaders load paths repeatedly and raises error if that happens. - # And because every new Rails::Application instance registers its own loader, we need to clear previously registered ones from Zeitwerk. - Zeitwerk::Registry.loaders.clear - - # Rails removes the support of multiple instances, which includes freezing some setting values. - # This is the workaround to avoid FrozenError. Related issue: https://github.com/rails/rails/issues/42319 - ActiveSupport::Dependencies.autoload_once_paths = [] - ActiveSupport::Dependencies.autoload_paths = [] - - # there are a few Rails initializers/finializers that register hook to the executor - # because the callbacks are stored inside the `ActiveSupport::Executor` class instead of an instance - # the callbacks duplicate after each time we initialize the application and cause issues when they're executed - ActiveSupport::Executor.reset_callbacks(:run) - ActiveSupport::Executor.reset_callbacks(:complete) - - # Rails uses this module to set a global context for its ErrorReporter feature. - # this needs to be cleared so previously set context won't pollute later reportings (see ErrorSubscriber). - ActiveSupport::ExecutionContext.clear - - ActionCable::Channel::Base.reset_callbacks(:subscribe) - ActionCable::Channel::Base.reset_callbacks(:unsubscribe) - - # Rails 7.1 stores the error reporter directly under the ActiveSupport class. - # So we need to make sure the subscriber is not subscribed unexpectedly before any tests - ActiveSupport.error_reporter.unsubscribe(Sentry::Rails::ErrorSubscriber) - - if ActiveSupport.respond_to?(:filter_parameters) - ActiveSupport.filter_parameters.clear - end -end - -def configure_app(app) - app.config.active_storage.service = :test - app.config.enable_reloading = false -end diff --git a/sentry-rails/spec/dummy/test_rails_app/db/schema.rb b/sentry-rails/spec/dummy/test_rails_app/db/schema.rb new file mode 100644 index 000000000..b73ea8e00 --- /dev/null +++ b/sentry-rails/spec/dummy/test_rails_app/db/schema.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +ActiveRecord::Schema.define(version: 2024_01_01_000003) do + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.integer "record_id", null: false + t.integer "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name" + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.integer "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + + create_table "comments", force: :cascade do |t| + t.integer "post_id" + end + + create_table "posts", force: :cascade do |t| + t.string "title" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end +end diff --git a/sentry-rails/spec/isolated/rails_logger_patch_spec.rb b/sentry-rails/spec/isolated/rails_logger_patch_spec.rb index 97a2cbc28..5e4f905ab 100644 --- a/sentry-rails/spec/isolated/rails_logger_patch_spec.rb +++ b/sentry-rails/spec/isolated/rails_logger_patch_spec.rb @@ -6,117 +6,108 @@ rescue LoadError end -require "logger" -require "sentry-ruby" -require "sentry/test_helper" - -require_relative "../dummy/test_rails_app/app" - RSpec.describe "Rails.logger with :logger patch" do - include Sentry::TestHelper + context "when :logger patch is enabled" do + before do + make_basic_app do |config, app| + config.enable_logs = true + config.enabled_patches = [:logger] + config.max_log_events = 10 + config.sdk_logger = Logger.new(nil) - let!(:app) do - make_basic_app do |config, app| - config.enable_logs = true - config.enabled_patches = [:logger] - config.max_log_events = 10 - config.sdk_logger = Logger.new(nil) + app.config.log_level = log_level + end - app.config.log_level = log_level + Rails.logger = Logger.new(nil) + Rails.logger.level = log_level end - end - let(:log_level) { ::Logger::DEBUG } - let(:log_output) { StringIO.new } + context "when Rails logger level is configured to debug" do + let(:log_level) { ::Logger::DEBUG } - before do - Rails.logger = Logger.new(log_output) - Rails.logger.level = log_level - end + it "captures Rails.logger calls when :logger patch is enabled" do + Rails.logger.debug("Test debug message") + Rails.logger.info("Test info message") + Rails.logger.warn("Test warning message") + Rails.logger.error("Test error message") + Rails.logger.fatal("Test fatal message") - context "when :logger patch is enabled" do - it "captures Rails.logger calls when :logger patch is enabled" do - Rails.logger.debug("Test debug message") - Rails.logger.info("Test info message") - Rails.logger.warn("Test warning message") - Rails.logger.error("Test error message") - Rails.logger.fatal("Test fatal message") - - Sentry.get_current_client.log_event_buffer.flush + Sentry.get_current_client.log_event_buffer.flush - expect(sentry_logs).not_to be_empty + expect(sentry_logs).not_to be_empty - log_messages = sentry_logs.map { |log| log[:body] } - expect(log_messages).to include( - "Test debug message", - "Test info message", - "Test warning message", - "Test error message", - "Test fatal message" - ) - - test_logs = sentry_logs.select { |log| log[:body].start_with?("Test ") } - log_levels = test_logs.map { |log| log[:level] } - expect(log_levels).to contain_exactly("debug", "info", "warn", "error", "fatal") - end + log_messages = sentry_logs.map { |log| log[:body] } + expect(log_messages).to include( + "Test debug message", + "Test info message", + "Test warning message", + "Test error message", + "Test fatal message" + ) + + test_logs = sentry_logs.select { |log| log[:body].start_with?("Test ") } + log_levels = test_logs.map { |log| log[:level] } + expect(log_levels).to contain_exactly("debug", "info", "warn", "error", "fatal") + end - it "captures Rails.logger calls with block syntax" do - Rails.logger.info { "Block message" } + it "captures Rails.logger calls with block syntax" do + Rails.logger.info { "Block message" } - Sentry.get_current_client.log_event_buffer.flush + Sentry.get_current_client.log_event_buffer.flush - expect(sentry_logs).not_to be_empty + expect(sentry_logs).not_to be_empty - log_messages = sentry_logs.map { |log| log[:body] } - expect(log_messages).to include("Block message") + log_messages = sentry_logs.map { |log| log[:body] } + expect(log_messages).to include("Block message") - block_log = sentry_logs.find { |log| log[:body] == "Block message" } - expect(block_log[:level]).to eq("info") - end + block_log = sentry_logs.find { |log| log[:body] == "Block message" } + expect(block_log[:level]).to eq("info") + end - it "captures Rails.logger calls with progname" do - Rails.logger.info("MyProgram") { "Message with progname" } + it "captures Rails.logger calls with progname" do + Rails.logger.info("MyProgram") { "Message with progname" } - Sentry.get_current_client.log_event_buffer.flush + Sentry.get_current_client.log_event_buffer.flush - expect(sentry_logs).not_to be_empty + expect(sentry_logs).not_to be_empty - log_messages = sentry_logs.map { |log| log[:body] } - expect(log_messages).to include("Message with progname") + log_messages = sentry_logs.map { |log| log[:body] } + expect(log_messages).to include("Message with progname") - progname_log = sentry_logs.find { |log| log[:body] == "Message with progname" } - expect(progname_log[:level]).to eq("info") - end + progname_log = sentry_logs.find { |log| log[:body] == "Message with progname" } + expect(progname_log[:level]).to eq("info") + end - it "does not capture Sentry SDK internal logs" do - Rails.logger.info(Sentry::Logger::PROGNAME) { "Internal Sentry message" } + it "does not capture Sentry SDK internal logs" do + Rails.logger.info(Sentry::Logger::PROGNAME) { "Internal Sentry message" } - Sentry.get_current_client.log_event_buffer.flush + Sentry.get_current_client.log_event_buffer.flush - log_messages = sentry_logs.map { |log| log[:body] } - expect(log_messages).not_to include("Internal Sentry message") - end + log_messages = sentry_logs.map { |log| log[:body] } + expect(log_messages).not_to include("Internal Sentry message") + end - it "strips whitespace from log messages" do - Rails.logger.info(" Message with whitespace ") + it "strips whitespace from log messages" do + Rails.logger.info(" Message with whitespace ") - Sentry.get_current_client.log_event_buffer.flush + Sentry.get_current_client.log_event_buffer.flush - expect(sentry_logs).not_to be_empty + expect(sentry_logs).not_to be_empty - log_messages = sentry_logs.map { |log| log[:body] } - expect(log_messages).to include("Message with whitespace") - end + log_messages = sentry_logs.map { |log| log[:body] } + expect(log_messages).to include("Message with whitespace") + end - it "handles non-string log messages" do - Rails.logger.info(12345) + it "handles non-string log messages" do + Rails.logger.info(12345) - Sentry.get_current_client.log_event_buffer.flush + Sentry.get_current_client.log_event_buffer.flush - expect(sentry_logs).not_to be_empty + expect(sentry_logs).not_to be_empty - log_messages = sentry_logs.map { |log| log[:body] } - expect(log_messages).to include("12345") + log_messages = sentry_logs.map { |log| log[:body] } + expect(log_messages).to include("12345") + end end context "when Rails logger level is configured to warn" do diff --git a/sentry-rails/spec/sentry/rails/activejob_spec.rb b/sentry-rails/spec/sentry/rails/activejob_spec.rb index c7f6c423a..05c491858 100644 --- a/sentry-rails/spec/sentry/rails/activejob_spec.rb +++ b/sentry-rails/spec/sentry/rails/activejob_spec.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "spec_helper" require_relative "../../support/test_jobs" RSpec.describe "without Sentry initialized", type: :job do @@ -13,10 +14,6 @@ end RSpec.describe "ActiveJob integration", type: :job do - before do - make_basic_app - end - let(:event) do transport.events.last.to_json_compatible end @@ -30,6 +27,10 @@ end describe "ActiveJob arguments serialization" do + before do + make_basic_app + end + it "serializes ActiveRecord arguments in globalid form" do post = Post.create! post2 = Post.create! @@ -48,8 +49,8 @@ { "bar" => "Sentry" }, { "integer" => 1, - "post" => "gid://rails-test-app/Post/#{post.id}", - "nested" => { "another_level" => { "post" => "gid://rails-test-app/Post/#{post2.id}" } } + "post" => post.to_global_id.to_s, + "nested" => { "another_level" => { "post" => post2.to_global_id.to_s } } } ] ) @@ -97,7 +98,7 @@ def post.to_global_id { "bar" => "Sentry" }, { "integer" => 1, - "post" => "gid://rails-test-app/Post/#{post.id}", + "post" => post.to_global_id.to_s, "range" => [1, 2, 3] } ] @@ -122,7 +123,7 @@ def post.to_global_id { "bar" => "Sentry" }, { "integer" => 1, - "post" => "gid://rails-test-app/Post/#{post.id}", + "post" => post.to_global_id.to_s, "range" => "#{range.first}...#{range.last}" } ] @@ -130,34 +131,40 @@ def post.to_global_id end end - it "adds useful context to extra" do - expect { FailedJob.perform_now }.to raise_error(FailedJob::TestError) + describe "handling context" do + before do + make_basic_app + end - expect(transport.events.size).to be(1) + it "adds useful context to extra" do + expect { FailedJob.perform_now }.to raise_error(FailedJob::TestError) - event = transport.events.last.to_json_compatible + expect(transport.events.size).to be(1) - expect(event.dig("extra", "active_job")).to eq("FailedJob") - expect(event.dig("extra", "job_id")).to be_a(String) - expect(event.dig("extra", "provider_job_id")).to be_nil - expect(event.dig("extra", "arguments")).to eq([]) + event = transport.events.last.to_json_compatible - expect(event.dig("tags", "job_id")).to eq(event.dig("extra", "job_id")) - expect(event.dig("tags", "provider_job_id")).to eq(event.dig("extra", "provider_job_id")) - last_frame = event.dig("exception", "values", 0, "stacktrace", "frames").last - expect(last_frame["vars"]).to include({ "a" => "1", "b" => "0" }) - end + expect(event.dig("extra", "active_job")).to eq("FailedJob") + expect(event.dig("extra", "job_id")).to be_a(String) + expect(event.dig("extra", "provider_job_id")).to be_nil + expect(event.dig("extra", "arguments")).to eq([]) - it "clears context" do - expect { FailedWithExtraJob.perform_now }.to raise_error(FailedWithExtraJob::TestError) + expect(event.dig("tags", "job_id")).to eq(event.dig("extra", "job_id")) + expect(event.dig("tags", "provider_job_id")).to eq(event.dig("extra", "provider_job_id")) + last_frame = event.dig("exception", "values", 0, "stacktrace", "frames").last + expect(last_frame["vars"]).to include({ "a" => "1", "b" => "0" }) + end - expect(transport.events.size).to be(1) + it "clears context" do + expect { FailedWithExtraJob.perform_now }.to raise_error(FailedWithExtraJob::TestError) - event = transport.events.last.to_json_compatible + expect(transport.events.size).to be(1) - expect(event["extra"]["foo"]).to eq("bar") + event = transport.events.last.to_json_compatible - expect(Sentry.get_current_scope.extra).to eq({}) + expect(event["extra"]["foo"]).to eq("bar") + + expect(Sentry.get_current_scope.extra).to eq({}) + end end context "with tracing enabled" do @@ -231,7 +238,11 @@ def perform end end - context 'using rescue_from' do + context "using rescue_from" do + before do + make_basic_app + end + it 'does not trigger Sentry' do expect_any_instance_of(RescuedActiveJob).to receive(:rescue_callback).once.and_call_original @@ -260,11 +271,9 @@ def perform context "when we are using an adapter which has a specific integration" do before do - Sentry.configuration.rails.skippable_job_adapters = ["ActiveJob::QueueAdapters::TestAdapter"] - end - - after do - Sentry.configuration.rails.skippable_job_adapters = [] + make_basic_app do |config| + config.rails.skippable_job_adapters = ["ActiveJob::QueueAdapters::TestAdapter"] + end end it "does not trigger sentry and re-raises" do @@ -274,6 +283,10 @@ def perform end context "with cron monitoring mixin" do + before do + make_basic_app + end + context "normal job" do it "returns #perform method's return value" do expect(NormalJobWithCron.perform_now).to eq("foo") @@ -342,6 +355,8 @@ def perform if defined?(JRUBY_VERSION) && JRUBY_VERSION == "9.4.12.0" skip "This crashes on jruby + rails 7.0.0.x. See https://github.com/getsentry/sentry-ruby/issues/2612" end + + make_basic_app end context "when active_job_report_on_retry_error is true" do diff --git a/sentry-rails/spec/sentry/rails/client_spec.rb b/sentry-rails/spec/sentry/rails/client_spec.rb index 86ef1e110..aff8a1a62 100644 --- a/sentry-rails/spec/sentry/rails/client_spec.rb +++ b/sentry-rails/spec/sentry/rails/client_spec.rb @@ -8,15 +8,7 @@ end let(:expected_initial_active_record_connections_count) do - if Gem::Version.new(Rails.version) < Gem::Version.new('7.2.0') - 1 - else - 0 - end - end - - before do - expect(ActiveRecord::Base.connection_pool.stat[:busy]).to eq(expected_initial_active_record_connections_count) + 1 end def send_events @@ -36,16 +28,19 @@ def send_events event end end + + # Verify initial connection state after app is set up + expect(ActiveRecord::Base.connection_pool.stat[:busy]).to be_between(0, expected_initial_active_record_connections_count) end it "doesn't hold the ActiveRecord connection after sending the event" do send_events - sleep(0.5) + sleep(1.0) expect(transport.events.count).to eq(5) - expect(ActiveRecord::Base.connection_pool.stat[:busy]).to eq(expected_initial_active_record_connections_count) + expect(ActiveRecord::Base.connection_pool.stat[:busy]).to be_between(0, expected_initial_active_record_connections_count) end end @@ -54,6 +49,9 @@ def send_events make_basic_app do |config| config.background_worker_threads = 5 end + + # Verify initial connection state after app is set up + expect(ActiveRecord::Base.connection_pool.stat[:busy]).to be_between(0, expected_initial_active_record_connections_count) end it "doesn't create any extra ActiveRecord connection when sending the event" do @@ -63,7 +61,7 @@ def send_events expect(transport.events.count).to eq(5) - expect(ActiveRecord::Base.connection_pool.stat[:busy]).to eq(expected_initial_active_record_connections_count) + expect(ActiveRecord::Base.connection_pool.stat[:busy]).to be_between(0, expected_initial_active_record_connections_count) end end end diff --git a/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb b/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb index c97b44eec..33625cada 100644 --- a/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb +++ b/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb @@ -177,7 +177,7 @@ attributes = log_event[:attributes] expect(attributes[:db_system][:value]).to eq("sqlite3") - expect(attributes[:db_name][:value]).to eq("db") + expect(attributes[:db_name][:value]).to eq("db.sqlite3") end end end diff --git a/sentry-rails/spec/sentry/rails/tracing/active_record_subscriber_spec.rb b/sentry-rails/spec/sentry/rails/tracing/active_record_subscriber_spec.rb index ef688b2b0..015fa9f54 100644 --- a/sentry-rails/spec/sentry/rails/tracing/active_record_subscriber_spec.rb +++ b/sentry-rails/spec/sentry/rails/tracing/active_record_subscriber_spec.rb @@ -112,7 +112,7 @@ def foo span = transaction[:spans][0] data = span[:data] - expect(data["db.name"]).to include("db") + expect(data["db.name"]).to include("db.sqlite3") expect(data["code.filepath"]).to eq(nil) expect(data["code.lineno"]).to eq(nil) expect(data["code.function"]).to eq(nil) @@ -144,7 +144,7 @@ def foo expect(cached_query_span[:tags]).to include({ cached: true }) data = cached_query_span[:data] - expect(data["db.name"]).to include("db") + expect(data["db.name"]).to include("db.sqlite3") expect(data["db.system"]).to eq("sqlite3") end end diff --git a/sentry-rails/spec/sentry/rails/tracing/active_storage_subscriber_spec.rb b/sentry-rails/spec/sentry/rails/tracing/active_storage_subscriber_spec.rb index 9a8a63b5a..54b4eda9c 100644 --- a/sentry-rails/spec/sentry/rails/tracing/active_storage_subscriber_spec.rb +++ b/sentry-rails/spec/sentry/rails/tracing/active_storage_subscriber_spec.rb @@ -51,30 +51,30 @@ expect(span.dig(:data, :key)).to be_nil expect(span[:trace_id]).to eq(request_transaction.dig(:contexts, :trace, :trace_id)) end + end - context "with send_default_pii = true" do - before do - make_basic_app do |config| - config.traces_sample_rate = 1.0 - config.send_default_pii = true - config.rails.tracing_subscribers = [described_class] - end + context "with send_default_pii = true" do + before do + make_basic_app do |config| + config.traces_sample_rate = 1.0 + config.rails.tracing_subscribers = [described_class] + config.send_default_pii = true end + end - it "records the :key in span.data" do - # make sure AnalyzeJob will be executed immediately - ActiveStorage::AnalyzeJob.queue_adapter.perform_enqueued_jobs = true + it "records the :key in span.data" do + # make sure AnalyzeJob will be executed immediately + ActiveStorage::AnalyzeJob.queue_adapter.perform_enqueued_jobs = true - p = Post.create! - get "/posts/#{p.id}/attach" + p = Post.create! + get "/posts/#{p.id}/attach" - request_transaction = transport.events.last.to_h - expect(request_transaction[:type]).to eq("transaction") - expect(request_transaction[:spans].count).to eq(2) + request_transaction = transport.events.last.to_h + expect(request_transaction[:type]).to eq("transaction") + expect(request_transaction[:spans].count).to eq(2) - span = request_transaction[:spans][1] - expect(span.dig(:data, :key)).to eq(p.cover.key) - end + span = request_transaction[:spans][1] + expect(span.dig(:data, :key)).to eq(p.cover.key) end end diff --git a/sentry-rails/spec/sentry/rails_spec.rb b/sentry-rails/spec/sentry/rails_spec.rb index ffbc4ca2e..1678a044f 100644 --- a/sentry-rails/spec/sentry/rails_spec.rb +++ b/sentry-rails/spec/sentry/rails_spec.rb @@ -63,16 +63,6 @@ end end - it "respects the logger set by user" do - logger = ::Logger.new(nil) - - make_basic_app do |config| - config.sdk_logger = logger - end - - expect(Sentry.configuration.sdk_logger).to eq(logger) - end - it "doesn't cause error if Rails::Logger is not present during SDK initialization" do Rails.logger = nil @@ -251,46 +241,49 @@ def capture_in_separate_process(exit_code:) expect(gem_frame["post_context"]).not_to be_empty expect(gem_frame["context_line"]).not_to be_empty end + end - it "doesn't filters exception backtrace if backtrace_cleanup_callback is overridden" do - make_basic_app do |config| - config.backtrace_cleanup_callback = lambda { |backtrace| backtrace } + context "with config.exceptions_app = self.routes" do + before do + make_basic_app do |config, app| + app.config.consider_all_requests_local = false + app.config.exceptions_app = app.routes end + end - get "/view_exception" + it "sets transaction to ControllerName#method" do + get "/exception" - traces = event.dig("exception", "values", 0, "stacktrace", "frames") - expect(traces.dig(-1, "filename")).to eq("inline template") - expect(traces.dig(-1, "function")).not_to be_nil - end + expect(transport.events.count).to eq(1) + last_event = transport.events.last + expect(last_event.transaction).to eq("HelloController#exception") + expect(transport.events.last.transaction_info).to eq({ source: :view }) + expect(response.body).to match(last_event.event_id) - context "with config.exceptions_app = self.routes" do - before do - make_basic_app do |config, app| - app.config.exceptions_app = app.routes - end - end + get "/posts" - it "sets transaction to ControllerName#method" do - get "/exception" + expect(transport.events.last.transaction).to eq("PostsController#index") + expect(transport.events.last.transaction_info).to eq({ source: :view }) + end - expect(transport.events.count).to eq(1) - last_event = transport.events.last - expect(last_event.transaction).to eq("HelloController#exception") - expect(transport.events.last.transaction_info).to eq({ source: :view }) - expect(response.body).to match(last_event.event_id) + it "sets correct request url" do + get "/exception" - get "/posts" + expect(event.dig("request", "url")).to eq("http://www.example.com/exception") + end + end - expect(transport.events.last.transaction).to eq("PostsController#index") - expect(transport.events.last.transaction_info).to eq({ source: :view }) + context "with overridden backtrace_cleanup_callback" do + it "doesn't filters exception backtrace if backtrace_cleanup_callback is overridden" do + make_basic_app do |config| + config.backtrace_cleanup_callback = lambda { |backtrace| backtrace } end - it "sets correct request url" do - get "/exception" + get "/view_exception" - expect(event.dig("request", "url")).to eq("http://www.example.com/exception") - end + traces = event.dig("exception", "values", 0, "stacktrace", "frames") + expect(traces.dig(-1, "filename")).to eq("inline template") + expect(traces.dig(-1, "function")).not_to be_nil end end @@ -306,6 +299,20 @@ def capture_in_separate_process(exit_code:) end end + context "with custom logger" do + before do + make_basic_app do |config| + config.sdk_logger = logger + end + end + + let(:logger) { ::Logger.new(nil) } + + it "respects the logger set by user" do + expect(Sentry.configuration.sdk_logger).to be(logger) + end + end + describe "error reporter integration", skip: Rails.version.to_f < 7.0 do context "when config.register_error_subscriber = false (default)" do before do diff --git a/sentry-rails/spec/spec_helper.rb b/sentry-rails/spec/spec_helper.rb index 85760781a..420f47dfa 100644 --- a/sentry-rails/spec/spec_helper.rb +++ b/sentry-rails/spec/spec_helper.rb @@ -1,17 +1,18 @@ # frozen_string_literal: true require "bundler/setup" + +ENV["RAILS_ENV"] = "test" + begin require "debug/prelude" rescue LoadError end require "sentry-ruby" -require "sentry/test_helper" +require "sentry-rails" -require 'rspec/retry' - -require 'simplecov' +require "simplecov" SimpleCov.start do project_name "sentry-rails" @@ -25,17 +26,20 @@ SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter end -# this already requires the sdk -require "dummy/test_rails_app/app" -# need to be required after rails is loaded from the above -require "rspec/rails" - -DUMMY_DSN = 'http://12345:67890@sentry.localdomain/sentry/42' - Dir["#{__dir__}/support/**/*.rb"].each { |file| require file } RAILS_VERSION = Rails.version.to_f +puts "\n" +puts "*"*80 +puts "Running tests for Rails #{RAILS_VERSION}" +puts "*"*80 +puts "\n" + +# Need to be required after rails is loaded from the dummy app +require "rspec/retry" +require "rspec/rails" + RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" @@ -47,7 +51,21 @@ c.syntax = :expect end - config.include(Sentry::TestHelper) + config.include(Sentry::Rails::TestHelper) + + config.before :suite do + Sentry::TestRailsApp.load_test_schema + end + + config.before :each do + # Make sure we reset the env in case something leaks in + ENV.delete('SENTRY_DSN') + ENV.delete('SENTRY_CURRENT_ENV') + ENV.delete('SENTRY_ENVIRONMENT') + ENV.delete('SENTRY_RELEASE') + ENV.delete('RAILS_ENV') + ENV.delete('RACK_ENV') + end config.after :each do Sentry::Rails::Tracing.unsubscribe_tracing_events @@ -65,17 +83,7 @@ reset_sentry_globals! - Rails.application = nil - end - - config.before :each do - # Make sure we reset the env in case something leaks in - ENV.delete('SENTRY_DSN') - ENV.delete('SENTRY_CURRENT_ENV') - ENV.delete('SENTRY_ENVIRONMENT') - ENV.delete('SENTRY_RELEASE') - ENV.delete('RAILS_ENV') - ENV.delete('RACK_ENV') + Rails.application&.cleanup! end config.include ActiveJob::TestHelper, type: :job diff --git a/sentry-rails/spec/support/test_helper.rb b/sentry-rails/spec/support/test_helper.rb new file mode 100644 index 000000000..b5397c889 --- /dev/null +++ b/sentry-rails/spec/support/test_helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "sentry/test_helper" +require "dummy/test_rails_app/config/application" + +module Sentry + module Rails + module TestHelper + module_function + + include Sentry::TestHelper + + def make_basic_app(&block) + Test::Application.define do |app| + app.initializer :configure_sentry do + Sentry.init do |config| + config.release = 'beta' + config.dsn = "http://12345:67890@sentry.localdomain:3000/sentry/42" + config.transport.transport_class = Sentry::DummyTransport + + # For sending events synchronously + config.background_worker_threads = 0 + config.include_local_variables = true + + yield(config, app) if block_given? + end + end + end + end + end + end +end diff --git a/sentry-rails/spec/versioned/2.7/activejob_spec.rb b/sentry-rails/spec/versioned/2.7/activejob_spec.rb index a67004111..ee4e4d3dd 100644 --- a/sentry-rails/spec/versioned/2.7/activejob_spec.rb +++ b/sentry-rails/spec/versioned/2.7/activejob_spec.rb @@ -41,7 +41,7 @@ { "bar" => "Sentry" }, { "integer" => 1, - "post" => "gid://rails-test-app/Post/#{post.id}", + "post" => post.to_global_id.to_s, "range_no_beginning" => "..#{range_no_beginning.last}", "range_no_end" => "#{range_no_end.first}.." } diff --git a/sentry-ruby.code-workspace b/sentry-ruby.code-workspace index c88597553..2f42418b9 100644 --- a/sentry-ruby.code-workspace +++ b/sentry-ruby.code-workspace @@ -37,7 +37,8 @@ "rubyLsp.rubyVersionManager": { "identifier": "auto" }, - "rubyLsp.formatter": "auto" + "rubyLsp.formatter": "auto", + "testing.gutterEnabled": false }, "extensions": { "recommendations": [