ruby_reactor 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,625 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyReactor
4
+ module RSpec
5
+ # rubocop:disable Metrics/ClassLength
6
+ class TestSubject
7
+ include ::RSpec::Mocks::ExampleMethods
8
+
9
+ attr_reader :reactor_instance, :run_result
10
+
11
+ def initialize(reactor_class:, inputs:, context: {}, async: nil, process_jobs: true)
12
+ @reactor_class = reactor_class
13
+ @inputs = inputs
14
+ @context_data = context
15
+ @async = async
16
+ @process_jobs = process_jobs
17
+ @interceptors = []
18
+ @executed = false
19
+ end
20
+
21
+ # --- Configuration DSL ---
22
+
23
+ def failing_at(step_name, *nested_steps, element_index: nil, &block)
24
+ @interceptors << {
25
+ type: :failure,
26
+ step_path: [step_name, *nested_steps],
27
+ conditions: { element_index: element_index, block: block }
28
+ }
29
+ self
30
+ end
31
+
32
+ # Intercept a step and provide a custom implementation
33
+ #
34
+ # @param step_name [Symbol, String] The name of the step to intercept
35
+ # @param nested_steps [Array<Symbol, String>] Path to nested steps if applicable
36
+ # @param element_index [Integer] Optional index for map steps
37
+ # @yield [args, context, original_impl] block to execute
38
+ # @yieldparam args [Hash] The arguments passed to the step
39
+ # @yieldparam context [RubyReactor::Context] The execution context
40
+ # @yieldparam original_impl [Proc] A proc that can be called to execute the original implementation:
41
+ # original_impl.call(args, context)
42
+ def mock_step(step_name, *nested_steps, element_index: nil, &block)
43
+ @interceptors << {
44
+ type: :mock,
45
+ step_path: [step_name, *nested_steps],
46
+ conditions: { element_index: element_index, block: block }
47
+ }
48
+ self
49
+ end
50
+
51
+ # Fluent API for mocking nested map steps
52
+ # @example
53
+ # reactor.map(:my_map).mock_step(:inner_step) { ... }
54
+ def map(step_name)
55
+ StepProxy.new(self, step_name)
56
+ end
57
+
58
+ # Fluent API for mocking nested compose steps
59
+ # @example
60
+ # reactor.compose(:my_sub_reactor).mock_step(:inner_step) { ... }
61
+ def composed(step_name)
62
+ # If already executed, return the traversed subject
63
+ return traverse_composed(step_name) if @executed
64
+
65
+ # Otherwise return a configuration proxy
66
+ StepProxy.new(self, step_name)
67
+ end
68
+ alias compose composed
69
+
70
+ # Proxy class for fluent mocking configuration
71
+ class StepProxy
72
+ def initialize(subject, step_name)
73
+ @subject = subject
74
+ @step_name = step_name
75
+ end
76
+
77
+ def mock_step(inner_step_name, *nested_steps, &block)
78
+ @subject.mock_step(@step_name, inner_step_name, *nested_steps, &block)
79
+ @subject # Return subject to allow chaining or calling run
80
+ end
81
+
82
+ # Support deep nesting?
83
+ def map(inner_step_name)
84
+ StepProxy.new(@subject, [@step_name, inner_step_name].flatten)
85
+ end
86
+
87
+ def composed(inner_step_name)
88
+ StepProxy.new(@subject, [@step_name, inner_step_name].flatten)
89
+ end
90
+ end
91
+
92
+ # --- Traversal ---
93
+
94
+ def map_elements(step_name)
95
+ ensure_executed!
96
+
97
+ # Check composed_contexts
98
+ entry = @reactor_instance.context.composed_contexts[step_name] ||
99
+ @reactor_instance.context.composed_contexts[step_name.to_s] ||
100
+ @reactor_instance.context.composed_contexts[step_name.to_sym]
101
+
102
+ return [] unless entry && entry[:type] == :map_ref
103
+
104
+ map_id = entry[:map_id]
105
+ storage = RubyReactor.configuration.storage_adapter
106
+
107
+ # This requires the storage adapter to implement retrieval of map element context IDs
108
+ # If it's not implemented in MemoryAdapter (if used), we might need to fallback?
109
+ # But tests use Redis.
110
+ child_ids = storage.retrieve_map_element_context_ids(map_id, @reactor_instance.class.name)
111
+
112
+ child_ids.map do |id|
113
+ klass = RubyReactor::Context.resolve_reactor_class(entry[:element_reactor_class])
114
+ child_instance = klass.find(id)
115
+ self.class.new(
116
+ reactor_class: child_instance.class,
117
+ inputs: child_instance.context.inputs,
118
+ context: child_instance.context,
119
+ async: @async,
120
+ process_jobs: @process_jobs
121
+ ).tap do |s|
122
+ s.instance_variable_set(:@executed, true)
123
+ s.instance_variable_set(:@reactor_instance, child_instance)
124
+ end
125
+ end
126
+ end
127
+
128
+ def map_element(step_name, index: 0)
129
+ elements = map_elements(step_name)
130
+ elements[index]
131
+ end
132
+
133
+ private
134
+
135
+ def traverse_composed(step_name)
136
+ ensure_executed!
137
+
138
+ entry = @reactor_instance.context.composed_contexts[step_name] ||
139
+ @reactor_instance.context.composed_contexts[step_name.to_s] ||
140
+ @reactor_instance.context.composed_contexts[step_name.to_sym]
141
+
142
+ unless entry && entry[:type] == :composed
143
+ # Try to find failed attempt if validation failure?
144
+ return nil
145
+ end
146
+
147
+ child_context = entry[:context]
148
+ child_instance = child_context.reactor_class.new(child_context)
149
+
150
+ self.class.new(
151
+ reactor_class: child_instance.class,
152
+ inputs: child_instance.context.inputs,
153
+ context: child_instance.context,
154
+ async: @async,
155
+ process_jobs: @process_jobs
156
+ ).tap do |s|
157
+ s.instance_variable_set(:@executed, true)
158
+ s.instance_variable_set(:@reactor_instance, child_instance)
159
+ end
160
+ end
161
+
162
+ public
163
+
164
+ def run_async(boolean)
165
+ @async = boolean
166
+ self
167
+ end
168
+
169
+ # --- Execution ---
170
+
171
+ def run
172
+ return self if @executed
173
+
174
+ # 1. Apply Interceptors (Dynamic Subclassing)
175
+ execution_class = prepare_execution_class
176
+
177
+ # 2. Capture Context ID
178
+ captured_context_id = nil
179
+
180
+ allow(RubyReactor::Context).to receive(:new).and_wrap_original do |m, *args|
181
+ ctx = m.call(*args)
182
+ captured_context_id ||= ctx.context_id
183
+ ctx
184
+ end
185
+
186
+ # 3. Native Run
187
+ if @async == false
188
+ allow(execution_class).to receive(:async?).and_return(false)
189
+ elsif @async == true
190
+ allow(execution_class).to receive(:async?).and_return(true)
191
+ end
192
+
193
+ @run_result = nil
194
+ if @process_jobs && defined?(Sidekiq::Testing)
195
+ # Ensure SidekiqAdapter is used to capture jobs in fake mode
196
+ allow(RubyReactor.configuration).to receive(:async_router).and_return(RubyReactor::SidekiqAdapter)
197
+
198
+ # Avoid nesting error which happens in Sidekiq 7+ if a mode is already set
199
+ begin
200
+ Sidekiq::Testing.fake! do
201
+ @run_result = execution_class.run(@inputs)
202
+ end
203
+ rescue Sidekiq::Testing::TestModeAlreadySetError
204
+ @run_result = execution_class.run(@inputs)
205
+ end
206
+ else
207
+ @run_result = execution_class.run(@inputs)
208
+ end
209
+
210
+ # 4. Reload
211
+ raise "Could not capture context ID during execution" unless captured_context_id
212
+
213
+ # Reload using the execution class (which might be the mocked subclass with unique name)
214
+ @reactor_instance = execution_class.find(captured_context_id)
215
+ # Update our reference to the class so future reloads (e.g. in result introspection) work
216
+ @reactor_class = execution_class
217
+ @executed = true
218
+ self
219
+ end
220
+
221
+ # --- Introspection (Auto-Run) ---
222
+
223
+ def result
224
+ ensure_executed!
225
+
226
+ ctx = @reactor_instance.context
227
+ status = ctx.status.to_s
228
+ case status
229
+ when "failed"
230
+ return ctx.failure_reason if ctx.failure_reason.is_a?(RubyReactor::Failure)
231
+
232
+ RubyReactor::Failure.new(ctx.failure_reason || {})
233
+ when "completed"
234
+ # Determine the success value
235
+ val = if @reactor_class.return_step
236
+ ctx.intermediate_results[@reactor_class.return_step.to_sym]
237
+ else
238
+ # Return result of the last executed step
239
+ # Execution trace contains: { step: name, ... }
240
+ # Trace does not strictly contain result, so we look up in intermediate_results
241
+ last_trace = ctx.execution_trace.last
242
+ if last_trace
243
+ step_name = last_trace[:step]
244
+ # Handle symbol/string mismatch
245
+ ctx.intermediate_results[step_name.to_sym] || ctx.intermediate_results[step_name.to_s]
246
+ end
247
+ end
248
+ RubyReactor::Success.new(val)
249
+ when "running"
250
+ # Try to determine if it is truly running or if we just missed the completion
251
+ if @process_jobs && defined?(Sidekiq::Testing)
252
+ # Force one more check
253
+ process_pending_jobs
254
+ # Reload status
255
+ @reactor_instance = @reactor_class.find(@reactor_instance.context.context_id)
256
+ return result unless @reactor_instance.context.status.to_s == "running"
257
+ end
258
+
259
+ # If still running, return a Pending/Running result instead of nil
260
+ # This allows matchers to report "expected success but was running"
261
+ RubyReactor::Failure("Reactor is still running (Async operations pending?)",
262
+ retryable: true)
263
+ when "paused"
264
+ RubyReactor::InterruptResult.new(
265
+ execution_id: ctx.context_id,
266
+ intermediate_results: ctx.intermediate_results
267
+ # We assume no error if paused normally
268
+ )
269
+ end
270
+ end
271
+
272
+ def success?
273
+ ensure_executed!
274
+ @reactor_instance.context.status.to_s == "completed"
275
+ end
276
+
277
+ def failure?
278
+ ensure_executed!
279
+ @reactor_instance.context.status.to_s == "failed"
280
+ end
281
+
282
+ # --- Interrupt Test Helpers ---
283
+
284
+ # Check if the reactor is paused at an interrupt
285
+ #
286
+ # @return [Boolean] true if the reactor is in paused state
287
+ def paused?
288
+ ensure_executed!
289
+ @reactor_instance.context.status.to_s == "paused"
290
+ end
291
+
292
+ # Get the current step where the reactor is paused (interrupt step)
293
+ # Note: When multiple interrupts are ready, this returns just one of them.
294
+ # Use `ready_interrupt_steps` to get all ready interrupt steps.
295
+ #
296
+ # @return [Symbol, nil] the name of the current interrupt step, or nil if not paused
297
+ def current_step
298
+ ensure_executed!
299
+ step = @reactor_instance.context.current_step
300
+ step&.to_sym
301
+ end
302
+
303
+ # Get all ready interrupt steps (steps that can be resumed)
304
+ # This is useful when multiple interrupts are waiting concurrently.
305
+ #
306
+ # @return [Array<Symbol>] list of ready interrupt step names
307
+ def ready_interrupt_steps
308
+ ensure_executed!
309
+ return [] unless paused?
310
+
311
+ # Build the dependency graph and get ready steps
312
+ graph = RubyReactor::DependencyGraph.new
313
+ graph_manager = RubyReactor::Executor::GraphManager.new(
314
+ @reactor_class, graph, @reactor_instance.context
315
+ )
316
+ graph_manager.build_and_validate!
317
+ graph_manager.mark_completed_steps_from_context
318
+
319
+ # Filter to only interrupt steps (using interrupt? predicate method)
320
+ ready = graph_manager.dependency_graph.ready_steps
321
+ ready.select { |step_config| step_config.respond_to?(:interrupt?) && step_config.interrupt? }
322
+ .map { |step_config| step_config.name.to_sym }
323
+ end
324
+
325
+ # Resume a paused reactor with the given payload
326
+ #
327
+ # @param payload [Hash] The data to provide to the interrupt step
328
+ # @param step [Symbol, String, nil] The specific interrupt step to resume.
329
+ # Required when multiple interrupts are ready. If not provided and only
330
+ # one interrupt is ready, that step will be used.
331
+ # @return [TestSubject] self for chaining and introspection
332
+ # @raise [Error::ValidationError] if the reactor is not paused, step is ambiguous, or payload is invalid
333
+ def resume(payload: {}, step: nil)
334
+ ensure_executed!
335
+
336
+ unless paused?
337
+ raise RubyReactor::Error::ValidationError,
338
+ "Cannot resume: reactor is not paused (status: #{@reactor_instance.context.status})"
339
+ end
340
+
341
+ step_name = determine_resume_step(step)
342
+
343
+ # Use the reactor's continue method
344
+ @reactor_instance.continue(payload: payload, step_name: step_name)
345
+
346
+ # Process any pending async jobs
347
+ process_pending_jobs if @process_jobs && defined?(Sidekiq::Testing)
348
+
349
+ # Reload the reactor instance to get updated state
350
+ @reactor_instance = @reactor_class.find(@reactor_instance.context.context_id)
351
+
352
+ # Return self for chaining and introspection
353
+ self
354
+ end
355
+
356
+ private
357
+
358
+ def determine_resume_step(step)
359
+ ready_steps = ready_interrupt_steps
360
+
361
+ if step
362
+ # User explicitly specified a step
363
+ step_sym = step.to_sym
364
+ unless ready_steps.include?(step_sym)
365
+ raise RubyReactor::Error::ValidationError,
366
+ "Cannot resume: step :#{step} is not ready. Ready steps: #{ready_steps.inspect}"
367
+ end
368
+ step_sym
369
+ elsif ready_steps.size == 1
370
+ # Only one step ready, use it
371
+ ready_steps.first
372
+ elsif ready_steps.size > 1
373
+ # Multiple steps ready, step is required
374
+ raise RubyReactor::Error::ValidationError,
375
+ "Cannot resume: multiple interrupt steps are ready (#{ready_steps.inspect}). " \
376
+ "Please specify which step to resume using: resume(step: :step_name, payload: {...})"
377
+ else
378
+ # Fallback to current_step (shouldn't happen normally)
379
+ current_step || raise(RubyReactor::Error::ValidationError, "Cannot resume: no ready interrupt steps found")
380
+ end
381
+ end
382
+
383
+ public
384
+
385
+ def step_result(name)
386
+ ensure_executed!
387
+ # Prefer intermediate_results as it is the data store
388
+ # Logic to handle symbol vs string mismatch
389
+ key_sym = name.to_sym
390
+ key_str = name.to_s
391
+
392
+ if @reactor_instance.context.intermediate_results.key?(key_sym)
393
+ @reactor_instance.context.intermediate_results[key_sym]
394
+ elsif @reactor_instance.context.intermediate_results.key?(key_str)
395
+ @reactor_instance.context.intermediate_results[key_str]
396
+ else
397
+ # Fallback to execution trace if available (e.g. for inspection)
398
+ entry = @reactor_instance.context.execution_trace.find { |t| t[:step].to_s == key_str }
399
+ entry ? entry[:result] : nil
400
+ end
401
+ end
402
+
403
+ def error
404
+ res = result
405
+ res.respond_to?(:error) ? res.error : nil
406
+ end
407
+
408
+ def ensure_executed!
409
+ run unless @executed
410
+
411
+ # Process jobs if status is running and processing is enabled
412
+ return unless @process_jobs && @reactor_instance.context.status.to_s == "running"
413
+
414
+ process_pending_jobs
415
+ end
416
+
417
+ private
418
+
419
+ def process_pending_jobs
420
+ return unless defined?(Sidekiq::Testing)
421
+
422
+ # Loop until no more jobs are being queued
423
+ # This handles batched map execution where jobs queue more jobs
424
+ max_iterations = 100
425
+ iterations = 0
426
+
427
+ while iterations < max_iterations
428
+ iterations += 1
429
+ jobs_processed = false
430
+
431
+ # Known worker classes to check
432
+ worker_classes = [
433
+ RubyReactor::SidekiqWorkers::Worker,
434
+ RubyReactor::SidekiqWorkers::MapElementWorker,
435
+ RubyReactor::SidekiqWorkers::MapExecutionWorker,
436
+ RubyReactor::SidekiqWorkers::MapCollectorWorker
437
+ ]
438
+
439
+ worker_classes.each do |worker_class|
440
+ while worker_class.jobs.any?
441
+ job = worker_class.jobs.shift
442
+ worker_class.new.perform(*job["args"])
443
+ jobs_processed = true
444
+ end
445
+ end
446
+
447
+ break unless jobs_processed
448
+ end
449
+
450
+ # Final reload
451
+ @reactor_instance = @reactor_class.find(@reactor_instance.context.context_id)
452
+ end
453
+
454
+ def prepare_execution_class
455
+ # Even if no interceptors, we might need to subclass to override async steps
456
+ return @reactor_class if @interceptors.empty? && @async != false
457
+
458
+ interceptors = @interceptors
459
+ force_sync = @async == false
460
+
461
+ execution_class = Class.new(@reactor_class) do
462
+ # 1. Copy configuration from parent
463
+ @steps = superclass.steps.dup
464
+ @inputs = superclass.inputs.dup
465
+ @input_validations = superclass.input_validations.dup
466
+ @middlewares = superclass.middlewares.dup
467
+ @return_step = superclass.return_step
468
+ @async = superclass.async?
469
+ @retry_defaults = superclass.instance_variable_get(:@retry_defaults)
470
+
471
+ # 2. Add Name Handling with Unique Registry Entry
472
+ # We must register a unique name so that if this reactor is reloaded (e.g. after async child completion),
473
+ # it resolves back to THIS mocked class, not the original superclass.
474
+ unique_name = "#{superclass.name}Mock#{object_id}"
475
+ define_singleton_method(:name) { unique_name }
476
+ RubyReactor::Registry.register(unique_name, self)
477
+
478
+ # 3. Apply Force Sync (Disable async on all steps)
479
+ if force_sync
480
+ @steps.each do |name, config|
481
+ next unless config.async?
482
+
483
+ # Clone and modify
484
+ new_config = config.clone
485
+ new_config.instance_variable_set(:@async, false)
486
+ @steps[name] = new_config
487
+ end
488
+ end
489
+ end
490
+
491
+ # 4. Apply Interceptors
492
+ apply_interceptors(execution_class, interceptors)
493
+
494
+ execution_class
495
+ end
496
+
497
+ def apply_interceptors(klass, interceptors)
498
+ # Group interceptors by the current level step
499
+ grouped = interceptors.group_by { |i| i[:step_path].first }
500
+
501
+ grouped.each do |target_step, step_interceptors|
502
+ step_config_orig = klass.steps[target_step]
503
+
504
+ unless step_config_orig
505
+ # Maybe it's a map step? We can't easily intercept inner steps from here
506
+ next
507
+ end
508
+
509
+ # Create a new StepConfig
510
+ step_config = step_config_orig.clone
511
+
512
+ # Check if we have nested interceptors
513
+ nested_interceptors = step_interceptors.select { |i| i[:step_path].size > 1 }
514
+
515
+ if nested_interceptors.any?
516
+ apply_nested_interceptors(step_config, nested_interceptors)
517
+ step_config.instance_variable_set(:@async, false)
518
+ end
519
+
520
+ # Apply direct interceptors (mocks/failures on this step)
521
+ direct_interceptors = step_interceptors.select { |i| i[:step_path].size == 1 }
522
+ direct_interceptors.each do |interceptor|
523
+ case interceptor[:type]
524
+ when :failure
525
+ apply_failure_interceptor(step_config, target_step)
526
+ when :mock
527
+ apply_mock_interceptor(step_config, target_step, step_config_orig, interceptor)
528
+ end
529
+ end
530
+
531
+ klass.steps[target_step] = step_config
532
+ end
533
+ end
534
+
535
+ def apply_nested_interceptors(step_config, interceptors)
536
+ # Determine if it's a map step or compose step based on arguments
537
+ # Map steps have :mapped_reactor_class in arguments
538
+ # Compose steps (ComposeStep logic) should also have it in arguments (passed via DSL builder)
539
+
540
+ args = step_config.arguments
541
+ target_reactor_class_source = nil
542
+ arg_key = nil
543
+
544
+ if args[:mapped_reactor_class]
545
+ target_reactor_class_source = args[:mapped_reactor_class][:source]
546
+ arg_key = :mapped_reactor_class
547
+ elsif args[:composed_reactor_class]
548
+ target_reactor_class_source = args[:composed_reactor_class][:source]
549
+ arg_key = :composed_reactor_class
550
+ end
551
+
552
+ return unless target_reactor_class_source.is_a?(RubyReactor::Template::Value)
553
+
554
+ original_child_reactor = target_reactor_class_source.value
555
+
556
+ # Dynamically subclass the child reactor
557
+ mocked_child_reactor = Class.new(original_child_reactor) do
558
+ define_singleton_method(:name) { original_child_reactor.name }
559
+ # Copy configuration
560
+ @steps = superclass.steps.dup
561
+ @inputs = superclass.inputs.dup
562
+ @input_validations = superclass.input_validations.dup
563
+ @middlewares = superclass.middlewares.dup
564
+ @return_step = superclass.return_step
565
+ @async = superclass.async?
566
+ @retry_defaults = superclass.instance_variable_get(:@retry_defaults)
567
+ end
568
+
569
+ # Recursively apply interceptors to the child reactor
570
+ # Shift path: [:map_step, :inner, :deep] -> [:inner, :deep]
571
+ child_interceptors = interceptors.map do |i|
572
+ i.merge(step_path: i[:step_path].drop(1))
573
+ end
574
+
575
+ apply_interceptors(mocked_child_reactor, child_interceptors)
576
+
577
+ # Replace the argument source with the mocked class
578
+ # We need to clone arguments hash to avoid mutating original config globally if shared?
579
+ # StepConfig arguments are usually unique per config instance (we cloned step_config)
580
+ # BUT arguments hash is shared references. We must dup it.
581
+
582
+ current_args = step_config.arguments
583
+ step_config.instance_variable_set(:@arguments, current_args.dup)
584
+
585
+ step_config.arguments[arg_key] = step_config.arguments[arg_key].dup if step_config.arguments[arg_key]
586
+
587
+ step_config.arguments[arg_key][:source] = RubyReactor::Template::Value.new(mocked_child_reactor)
588
+ end
589
+
590
+ def apply_failure_interceptor(step_config, target_step)
591
+ failure_impl = lambda do |_input, _context|
592
+ RubyReactor::Failure("Simulated failure at #{target_step}")
593
+ end
594
+
595
+ step_config.instance_variable_set(:@run_block, failure_impl)
596
+ end
597
+
598
+ def apply_mock_interceptor(step_config, target_step, step_config_orig, interceptor)
599
+ mock_block = interceptor[:conditions][:block]
600
+
601
+ # Prepare original implementation call
602
+ original_impl = if step_config_orig.has_run_block?
603
+ step_config_orig.run_block
604
+ elsif step_config_orig.has_impl?
605
+ ->(args, ctx) { step_config_orig.impl.run(args, ctx) }
606
+ else
607
+ ->(_, _) { raise "No implementation found for #{target_step}" }
608
+ end
609
+
610
+ # Create the new implementation that wraps the user block
611
+ wrapper_impl = lambda do |args, context|
612
+ if mock_block.arity == 3
613
+ mock_block.call(args, context, original_impl)
614
+ else
615
+ mock_block.call(args, context)
616
+ end
617
+ end
618
+
619
+ step_config.instance_variable_set(:@run_block, wrapper_impl)
620
+ step_config.instance_variable_set(:@async, false)
621
+ end
622
+ end
623
+ # rubocop:enable Metrics/ClassLength
624
+ end
625
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rspec/helpers"
4
+ require_relative "rspec/matchers"
5
+ require_relative "rspec/test_subject"
6
+
7
+ module RubyReactor
8
+ module RSpec
9
+ def self.configure(config)
10
+ require_relative "rspec/step_executor_patch"
11
+
12
+ config.include RubyReactor::RSpec::Helpers
13
+ config.include RubyReactor::RSpec::Matchers
14
+
15
+ ::RubyReactor::Executor::StepExecutor.prepend(RubyReactor::RSpec::StepExecutorPatch)
16
+ end
17
+ end
18
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyReactor
4
- class AsyncRouter
4
+ class SidekiqAdapter
5
5
  def self.perform_async(serialized_context, reactor_class_name = nil, intermediate_results: {})
6
6
  job_id = SidekiqWorkers::Worker.perform_async(serialized_context, reactor_class_name)
7
7
  context = ContextSerializer.deserialize(serialized_context)
@@ -20,7 +20,7 @@ module RubyReactor
20
20
  def self.perform_map_element_async(map_id:, element_id:, index:, serialized_inputs:, reactor_class_info:,
21
21
  strict_ordering:, parent_context_id:, parent_reactor_class_name:, step_name:,
22
22
  batch_size: nil, serialized_context: nil, fail_fast: nil)
23
- RubyReactor::SidekiqWorkers::MapElementWorker.perform_async(
23
+ job_id = RubyReactor::SidekiqWorkers::MapElementWorker.perform_async(
24
24
  {
25
25
  "map_id" => map_id,
26
26
  "element_id" => element_id,
@@ -36,6 +36,7 @@ module RubyReactor
36
36
  "fail_fast" => fail_fast
37
37
  }
38
38
  )
39
+ RubyReactor::AsyncResult.new(job_id: job_id)
39
40
  end
40
41
 
41
42
  def self.perform_map_element_in(delay, map_id:, element_id:, index:, serialized_inputs:, reactor_class_info:,
@@ -63,7 +64,7 @@ module RubyReactor
63
64
  # rubocop:disable Metrics/ParameterLists
64
65
  def self.perform_map_collection_async(parent_context_id:, map_id:, parent_reactor_class_name:, step_name:,
65
66
  strict_ordering:, timeout:)
66
- RubyReactor::SidekiqWorkers::MapCollectorWorker.perform_async(
67
+ job_id = RubyReactor::SidekiqWorkers::MapCollectorWorker.perform_async(
67
68
  {
68
69
  "parent_context_id" => parent_context_id,
69
70
  "map_id" => map_id,
@@ -73,12 +74,15 @@ module RubyReactor
73
74
  "timeout" => timeout
74
75
  }
75
76
  )
77
+ RubyReactor::AsyncResult.new(job_id: job_id)
76
78
  end
77
- # rubocop:enable Metrics/ParameterLists
78
79
 
80
+ # rubocop:enable Metrics/ParameterLists
81
+ # rubocop:disable Metrics/ParameterLists
79
82
  def self.perform_map_execution_async(map_id:, serialized_inputs:, reactor_class_info:, strict_ordering:,
80
83
  parent_context_id:, parent_reactor_class_name:, step_name:, fail_fast: nil)
81
- RubyReactor::SidekiqWorkers::MapExecutionWorker.perform_async(
84
+ # rubocop:enable Metrics/ParameterLists
85
+ job_id = RubyReactor::SidekiqWorkers::MapExecutionWorker.perform_async(
82
86
  {
83
87
  "map_id" => map_id,
84
88
  "serialized_inputs" => serialized_inputs,
@@ -90,6 +94,7 @@ module RubyReactor
90
94
  "fail_fast" => fail_fast
91
95
  }
92
96
  )
97
+ RubyReactor::AsyncResult.new(job_id: job_id)
93
98
  end
94
99
  end
95
100
  end