1010require "action_controller/railtie"
1111require "active_record/railtie"
1212require "active_job/railtie"
13+ require "time"
14+
15+ # Point the broker-backed adapters at Redis. Sidekiq reads REDIS_URL on
16+ # its own; Resque does not, so wire it up explicitly. Defaults to a local
17+ # Redis when REDIS_URL is unset (e.g. running outside Docker Compose).
18+ redis_url = ENV . fetch ( "REDIS_URL" , "redis://localhost:6379" )
19+ Resque . redis = redis_url if defined? ( Resque )
1320
1421class RailsMiniApp < Rails ::Application
1522 config . hosts = nil
@@ -20,6 +27,29 @@ class RailsMiniApp < Rails::Application
2027 config . api_only = true
2128 config . force_ssl = false
2229
30+ # Select the ActiveJob queue adapter from the environment. This must be
31+ # assigned in the application body (not inside an `initializer` block):
32+ # ActiveJob's own `active_job.set_configs` initializer reads
33+ # `config.active_job.queue_adapter` and applies it via an `on_load`
34+ # hook that fires during boot, before app-defined initializers run. An
35+ # assignment made from an initializer would therefore be a silent no-op
36+ # and every adapter would fall back to the default :async.
37+ SUPPORTED_ACTIVE_JOB_ADAPTERS = {
38+ "async" => :async ,
39+ "inline" => :inline ,
40+ "sidekiq" => :sidekiq ,
41+ "resque" => :resque ,
42+ "delayed_job" => :delayed_job
43+ } . freeze
44+
45+ adapter_name = ENV . fetch ( "SENTRY_E2E_ACTIVE_JOB_ADAPTER" , "async" ) . to_s . downcase
46+ unless SUPPORTED_ACTIVE_JOB_ADAPTERS . key? ( adapter_name )
47+ raise "Unsupported ActiveJob adapter: #{ adapter_name } "
48+ end
49+
50+ config . active_job . queue_adapter = SUPPORTED_ACTIVE_JOB_ADAPTERS [ adapter_name ]
51+ config . x . active_job_adapter_name = adapter_name
52+
2353 def debug_log_path
2454 @log_path ||= begin
2555 path = Pathname ( __dir__ ) . join ( "../../../log" )
@@ -211,48 +241,64 @@ def set_cors_headers
211241class JobsController < ActionController ::Base
212242 before_action :set_cors_headers
213243
214- def sample_job
215- job = SampleJob . perform_later ( "Hello from Rails mini app!" )
244+ JOB_CLASSES = {
245+ "sample" => SampleJob ,
246+ "database" => DatabaseJob ,
247+ "failing" => FailingJob
248+ } . freeze
216249
217- Sentry . logger . info ( "SampleJob enqueued" , job_id : job . job_id )
250+ def enqueue
251+ job_type = params [ :job_type ] || params [ :id ] || params [ :job ] || "sample"
252+ job_class = JOB_CLASSES [ job_type . to_s ]
253+ raise ActionController ::BadRequest . new ( "Unsupported job type: #{ job_type } " ) unless job_class
218254
219- render json : {
220- message : "SampleJob enqueued successfully" ,
221- job_id : job . job_id ,
222- job_class : job . class . name
223- }
224- end
255+ args = Array ( params [ :args ] || [ ] )
256+ args = JSON . parse ( args ) if args . is_a? ( String ) && args . strip . start_with? ( "[" )
225257
226- def database_job
227- title = params [ :title ] || "Test Post from Job"
228- job = DatabaseJob . perform_later ( title )
258+ job = schedule_job ( job_class , args )
229259
230- Sentry . logger . info ( "DatabaseJob enqueued" , job_id : job . job_id , post_title : title )
260+ Sentry . logger . info (
261+ "#{ job_class . name } enqueued" ,
262+ job_id : job . job_id ,
263+ job_class : job . class . name ,
264+ args : args
265+ )
231266
232- render json : {
233- message : "DatabaseJob enqueued successfully" ,
267+ response_body = {
268+ message : "#{ job_class . name } enqueued successfully" ,
234269 job_id : job . job_id ,
235270 job_class : job . class . name ,
236- post_title : title
271+ args : args
237272 }
238- end
239273
240- def failing_job
241- should_fail = params [ :should_fail ] != "false"
242- job = FailingJob . perform_later ( should_fail )
274+ if job_type . to_s == "database"
275+ response_body [ :post_title ] = args [ 0 ] || "Test Post from Job"
276+ elsif job_type . to_s == "failing"
277+ response_body [ :should_fail ] = args . empty? ? true : args . first
278+ end
243279
244- Sentry . logger . info ( "FailingJob enqueued" , job_id : job . job_id , should_fail : should_fail )
280+ render json : response_body
281+ end
245282
283+ def active_job_adapter
246284 render json : {
247- message : "FailingJob enqueued successfully" ,
248- job_id : job . job_id ,
249- job_class : job . class . name ,
250- should_fail : should_fail
285+ adapter : Rails . configuration . x . active_job_adapter_name ,
286+ queue_adapter : ActiveJob ::Base . queue_adapter . class . name
251287 }
252288 end
253289
254290 private
255291
292+ def schedule_job ( job_class , args )
293+ if params [ :wait_seconds ] . present?
294+ job_class . set ( wait : params [ :wait_seconds ] . to_i . seconds ) . perform_later ( *args )
295+ elsif params [ :wait_until ] . present?
296+ job_class . set ( wait_until : Time . parse ( params [ :wait_until ] ) ) . perform_later ( *args )
297+ else
298+ job_class . perform_later ( *args )
299+ end
300+ end
301+
256302 def set_cors_headers
257303 response . headers [ 'Access-Control-Allow-Origin' ] = '*'
258304 response . headers [ 'Access-Control-Allow-Methods' ] = 'GET, POST, PUT, DELETE, OPTIONS'
@@ -262,23 +308,45 @@ def set_cors_headers
262308
263309RailsMiniApp . initialize!
264310
265- ActiveRecord ::Schema . define do
266- create_table :posts , force : true do |t |
267- t . string :title , null : false
268- t . text :content
269- t . timestamps
270- end
311+ # The web process owns schema setup. The worker (worker.rb) boots the same
312+ # app in parallel and sets SENTRY_E2E_SKIP_DB_SETUP=true to skip this block,
313+ # avoiding a concurrent `force: true` drop/create race on the shared SQLite
314+ # file; it waits for these tables to appear before processing jobs.
315+ unless ENV [ "SENTRY_E2E_SKIP_DB_SETUP" ] == "true"
316+ ActiveRecord ::Schema . define do
317+ create_table :posts , force : true do |t |
318+ t . string :title , null : false
319+ t . text :content
320+ t . timestamps
321+ end
322+
323+ create_table :users , force : true do |t |
324+ t . string :name , null : false
325+ t . string :email
326+ t . timestamps
327+ end
271328
272- create_table :users , force : true do |t |
273- t . string :name , null : false
274- t . string :email
275- t . timestamps
329+ # Backing store for the :delayed_job adapter. Created unconditionally so
330+ # the same schema works regardless of which adapter the worker uses.
331+ create_table :delayed_jobs , force : true do |t |
332+ t . integer :priority , default : 0 , null : false
333+ t . integer :attempts , default : 0 , null : false
334+ t . text :handler , null : false
335+ t . text :last_error
336+ t . datetime :run_at
337+ t . datetime :locked_at
338+ t . datetime :failed_at
339+ t . string :locked_by
340+ t . string :queue
341+ t . timestamps null : true
342+ end
343+ add_index :delayed_jobs , [ :priority , :run_at ] , name : "delayed_jobs_priority"
276344 end
277- end
278345
279- Post . create! ( title : "Welcome Post" , content : "Welcome to the Rails mini app!" )
280- Post . create! ( title : "Sample Post" , content : "This is a sample post for testing." )
281- User . create! ( name : "Test User" , email : "test@example.com" )
346+ Post . create! ( title : "Welcome Post" , content : "Welcome to the Rails mini app!" )
347+ Post . create! ( title : "Sample Post" , content : "This is a sample post for testing." )
348+ User . create! ( name : "Test User" , email : "test@example.com" )
349+ end
282350
283351RailsMiniApp . routes . draw do
284352 get '/health' , to : 'events#health'
@@ -291,9 +359,9 @@ def set_cors_headers
291359 post '/posts' , to : 'posts#create'
292360 get '/posts/:id' , to : 'posts#show'
293361
294- post '/jobs/sample ' , to : 'jobs#sample_job '
295- post '/jobs/database ' , to : 'jobs#database_job '
296- post '/jobs/failing ' , to : 'jobs#failing_job '
362+ post '/jobs/enqueue ' , to : 'jobs#enqueue '
363+ post '/jobs/:job_type ' , to : 'jobs#enqueue '
364+ get '/jobs/adapter ' , to : 'jobs#active_job_adapter '
297365
298366 match '*path' , to : proc { |env |
299367 [ 200 , {
0 commit comments