Module: GenevaDrive::TestHelpers
- Defined in:
- lib/geneva_drive/test_helpers.rb
Overview
Test helpers for testing workflows in your application.
Include this module in your test classes to get access to workflow testing utilities.
Instance Method Summary collapse
-
#assert_cursor(workflow, expected_cursor) ⇒ void
Asserts that a resumable step's cursor matches the expected value.
-
#assert_signal_state(workflow, signal_name, state) ⇒ void
Asserts that a signal with the given name exists in the given state.
-
#assert_step_executed(workflow, step_name, state: "completed") ⇒ void
Asserts that a workflow has executed a specific step.
-
#assert_step_has_successor(workflow, step_name) ⇒ void
Asserts that a step completed and has a scheduled successor execution.
-
#assert_waiting_for_signal(workflow, signal_name = nil) ⇒ void
Asserts that the workflow is parked waiting for a signal.
-
#assert_workflow_state(workflow, expected_state) ⇒ void
Asserts that a workflow is in a specific state.
-
#perform_next_step(workflow) ⇒ GenevaDrive::StepExecution?
Executes only the next pending step of a workflow.
-
#perform_step_inline(workflow, step_name = nil) ⇒ GenevaDrive::StepExecution
Executes a step inline, bypassing normal workflow progression.
-
#run_iterations(workflow, count:) ⇒ GenevaDrive::StepExecution
Runs the current (resumable) step for a limited number of iterations, then lets it interrupt and create a successor execution.
-
#speedrun_current_step(workflow, max_executions: 100) ⇒ GenevaDrive::StepExecution
Runs the current step to completion without advancing to the next step.
-
#speedrun_workflow(workflow, max_iterations: 100) ⇒ GenevaDrive::Workflow
Executes all steps of a workflow synchronously for testing.
Instance Method Details
#assert_cursor(workflow, expected_cursor) ⇒ void
This method returns an undefined value.
Asserts that a resumable step's cursor matches the expected value.
272 273 274 275 276 277 278 279 |
# File 'lib/geneva_drive/test_helpers.rb', line 272 def assert_cursor(workflow, expected_cursor) workflow.reload execution = workflow.current_execution actual = execution&.cursor_value assert_equal expected_cursor, actual, "Expected cursor #{expected_cursor.inspect}, got #{actual.inspect}" end |
#assert_signal_state(workflow, signal_name, state) ⇒ void
This method returns an undefined value.
Asserts that a signal with the given name exists in the given state.
338 339 340 341 342 343 344 |
# File 'lib/geneva_drive/test_helpers.rb', line 338 def assert_signal_state(workflow, signal_name, state) signal = workflow.signals.where(name: signal_name.to_s).order(:created_at, :id).last assert signal, "Expected a #{signal_name.inspect} signal on #{workflow.class.name}, but none was found" assert_equal state.to_s, signal.state, "Expected signal #{signal_name.inspect} to be #{state}, but was #{signal.state}" end |
#assert_step_executed(workflow, step_name, state: "completed") ⇒ void
This method returns an undefined value.
Asserts that a workflow has executed a specific step.
154 155 156 157 158 159 160 161 162 |
# File 'lib/geneva_drive/test_helpers.rb', line 154 def assert_step_executed(workflow, step_name, state: "completed") step_execution = workflow.step_executions.find_by( step_name: step_name.to_s, state: state ) assert step_execution, "Expected step '#{step_name}' to be #{state}, but it was not found" end |
#assert_step_has_successor(workflow, step_name) ⇒ void
This method returns an undefined value.
Asserts that a step completed and has a scheduled successor execution. Used to verify that a resumable step was interrupted and will continue.
291 292 293 294 295 296 297 298 299 |
# File 'lib/geneva_drive/test_helpers.rb', line 291 def assert_step_has_successor(workflow, step_name) workflow.reload completed = workflow.step_executions.find_by(step_name: step_name.to_s, state: "completed") assert completed, "Expected completed execution for step #{step_name}" successor = completed.successor assert successor, "Expected successor execution for step #{step_name}, but none found" assert successor.scheduled?, "Expected successor to be scheduled, but was #{successor.state}" end |
#assert_waiting_for_signal(workflow, signal_name = nil) ⇒ void
This method returns an undefined value.
Asserts that the workflow is parked waiting for a signal.
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/geneva_drive/test_helpers.rb', line 312 def assert_waiting_for_signal(workflow, signal_name = nil) workflow.reload execution = workflow.step_executions.where(state: "waiting").first assert execution, "Expected #{workflow.class.name} to be waiting for a signal, but no parked step execution was found " \ "(current execution: #{workflow.current_execution&.state.inspect})" return unless signal_name matcher = execution.step_definition&.signal_matcher assert_equal signal_name.to_s, matcher_name(matcher), "Expected step '#{execution.step_name}' to be waiting for #{signal_name.inspect}, " \ "but it waits for #{matcher}" end |
#assert_workflow_state(workflow, expected_state) ⇒ void
This method returns an undefined value.
Asserts that a workflow is in a specific state.
174 175 176 177 178 |
# File 'lib/geneva_drive/test_helpers.rb', line 174 def assert_workflow_state(workflow, expected_state) workflow.reload assert_equal expected_state.to_s, workflow.state, "Expected workflow to be #{expected_state}, but was #{workflow.state}" end |
#perform_next_step(workflow) ⇒ GenevaDrive::StepExecution?
Executes only the next pending step of a workflow.
Useful for testing step-by-step behavior and inspecting state between steps.
79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/geneva_drive/test_helpers.rb', line 79 def perform_next_step(workflow) workflow.reload step_execution = workflow.current_execution return nil unless step_execution raise_if_parked!(step_execution, "perform_next_step") step_execution.execute! workflow.reload step_execution end |
#perform_step_inline(workflow, step_name = nil) ⇒ GenevaDrive::StepExecution
Executes a step inline, bypassing normal workflow progression.
Creates a step execution for the given step and executes it immediately. This is a testing shortcut that allows you to test individual steps without running through the entire flow.
When called without a step name, executes the current step.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/geneva_drive/test_helpers.rb', line 112 def perform_step_inline(workflow, step_name = nil) workflow.reload step_name ||= workflow.next_step_name step_def = workflow.class.steps.find { |s| s.name == step_name.to_s } unless step_def available = workflow.class.steps.map(&:name).join(", ") raise ArgumentError, "Step '#{step_name}' is not defined in #{workflow.class.name}. " \ "Available steps: #{available}" end # Cancel any existing scheduled or parked step executions to satisfy the # uniqueness constraint workflow.step_executions.where(state: %w[scheduled waiting]).update_all( state: "canceled", outcome: "canceled", canceled_at: Time.current ) step_execution = workflow.step_executions.create!( step_name: step_def.name, state: "scheduled", scheduled_for: Time.current ) step_execution.execute! workflow.reload step_execution end |
#run_iterations(workflow, count:) ⇒ GenevaDrive::StepExecution
Runs the current (resumable) step for a limited number of iterations, then lets it interrupt and create a successor execution.
Useful for testing partial progress and cursor state of resumable steps.
250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/geneva_drive/test_helpers.rb', line 250 def run_iterations(workflow, count:) workflow.reload step_execution = workflow.current_execution raise "No current step execution to run" unless step_execution step_execution.execute!(max_iterations: count) workflow.reload step_execution.reload step_execution end |
#speedrun_current_step(workflow, max_executions: 100) ⇒ GenevaDrive::StepExecution
Runs the current step to completion without advancing to the next step.
For regular steps, this is equivalent to perform_next_step.
For resumable steps, this disables interruption checks and runs until
the step completes (or fails/cancels/skips).
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/geneva_drive/test_helpers.rb', line 197 def speedrun_current_step(workflow, max_executions: 100) workflow.reload step_execution = workflow.current_execution return nil unless step_execution raise_if_parked!(step_execution, "speedrun_current_step") executions = 0 original_step_name = step_execution.step_name loop do step_execution.reload raise_if_parked!(step_execution, "speedrun_current_step") # Follow the execution chain: a completed execution that spawned a # successor means the step is still going (suspend! and skip_to! create # successors even with interruption checks disabled). if GenevaDrive::StepExecution.resumable_columns? && step_execution.completed? && (successor = step_execution.successor) step_execution = successor next end break if step_execution.completed? || step_execution.failed? || step_execution.canceled? || step_execution.skipped? # interruptible: false so the step runs to completion step_execution.execute!(interruptible: false) executions += 1 if executions >= max_executions raise "speedrun_current_step exceeded max_executions (#{max_executions}). " \ "Step '#{original_step_name}' may be in an infinite loop." end end workflow.reload step_execution.reload step_execution end |
#speedrun_workflow(workflow, max_iterations: 100) ⇒ GenevaDrive::Workflow
Executes all steps of a workflow synchronously for testing.
This method runs through all scheduled steps, executing each one immediately regardless of wait times. Resumable steps are run to completion (interruption limits are disabled).
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/geneva_drive/test_helpers.rb', line 39 def speedrun_workflow(workflow, max_iterations: 100) iterations = 0 loop do workflow.reload break if %w[finished canceled paused].include?(workflow.state) step_execution = workflow.current_execution break unless step_execution raise_if_parked!(step_execution, "speedrun_workflow") # interruptible: false so resumable steps run to completion step_execution.execute!(interruptible: false) iterations += 1 if iterations >= max_iterations raise "speedrun_workflow exceeded max_iterations (#{max_iterations}). " \ "Workflow may be in an infinite loop." end end workflow.reload end |