busybee 0.2.0 → 0.3.0

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,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Busybee
6
+ # Operator-specified runtime configuration, typically from CLI flags or YAML.
7
+ #
8
+ # Two-phase lifecycle:
9
+ # 1. Constructed sparse (only fields the operator explicitly set)
10
+ # 2. After resolve_for(worker_class), fully resolved through the precedence chain:
11
+ # per-worker RuntimeConfig → global RuntimeConfig → worker DSL → gem defaults
12
+ #
13
+ # Fields are divided into two groups:
14
+ # - Worker-scoped: participate in the full 4-level precedence chain including
15
+ # per-worker overrides. Passed to runner constructors.
16
+ # - Process-wide: resolve at global RC → gem default only (no per-worker step).
17
+ # Applied to gem-level config before runners start.
18
+ #
19
+ # Runners hold the resolved config at runtime.
20
+ class RuntimeConfig
21
+ VALID_WORKER_MODES = %i[polling streaming hybrid].freeze
22
+ VALID_LOG_FORMATS = %i[text json].freeze
23
+
24
+ # Top-level keys allowed in YAML config (worker-scoped fields + workers).
25
+ WORKER_SCOPED_KEYS = %i[worker_mode backpressure_delay max_jobs request_timeout
26
+ buffer buffer_throttle job_timeout backoff].freeze
27
+ VALID_YAML_KEYS = (WORKER_SCOPED_KEYS + %i[workers]).freeze
28
+ PROCESS_WIDE_KEYS = %i[log_format worker_name cluster_address].freeze
29
+
30
+ # Parses a YAML config file and returns a kwargs hash suitable for
31
+ # RuntimeConfig.new(**result). Raw YAML types flow through — the
32
+ # constructor handles coercion (e.g., string → symbol for worker_mode).
33
+ def self.parse_yaml(path)
34
+ raw = YAML.safe_load_file(path) || {}
35
+ result = {}
36
+
37
+ raw.each do |key, value|
38
+ sym_key = key.to_sym
39
+ validate_yaml_key!(sym_key)
40
+ if sym_key == :workers
41
+ result[:workers] = parse_workers(value)
42
+ else
43
+ result[sym_key] = value
44
+ end
45
+ end
46
+
47
+ result
48
+ end
49
+
50
+ def self.validate_yaml_key!(key)
51
+ if PROCESS_WIDE_KEYS.include?(key)
52
+ raise ArgumentError, "#{key} is CLI-only and cannot be set in YAML. Use the corresponding CLI flag instead."
53
+ end
54
+ return if VALID_YAML_KEYS.include?(key)
55
+
56
+ raise ArgumentError,
57
+ "Unrecognized YAML key: #{key}. Valid keys: #{VALID_YAML_KEYS.join(', ')}"
58
+ end
59
+ private_class_method :validate_yaml_key!
60
+
61
+ def self.parse_workers(workers_list)
62
+ return {} unless workers_list
63
+
64
+ workers_list.each_with_object({}) do |entry, acc|
65
+ name, overrides = case entry
66
+ when String then [entry, {}]
67
+ when Hash then extract_worker_entry(entry)
68
+ end
69
+ validate_worker_override_keys!(name, overrides)
70
+ acc[name.to_s] = overrides
71
+ end
72
+ end
73
+ private_class_method :parse_workers
74
+
75
+ # A worker entry hash comes in two forms depending on YAML indentation:
76
+ # Nested: { "Worker" => { "max_jobs" => 32 } }
77
+ # Flat: { "Worker" => nil, "max_jobs" => 32 }
78
+ def self.extract_worker_entry(hash)
79
+ first_key, first_value = hash.first
80
+ if first_value.is_a?(Hash)
81
+ [first_key, first_value.transform_keys(&:to_sym)]
82
+ else
83
+ overrides = hash.compact.transform_keys(&:to_sym)
84
+ [first_key, overrides]
85
+ end
86
+ end
87
+ private_class_method :extract_worker_entry
88
+
89
+ def self.validate_worker_override_keys!(worker_name, overrides)
90
+ unknown = overrides.keys - WORKER_SCOPED_KEYS
91
+ return if unknown.empty?
92
+
93
+ raise ArgumentError,
94
+ "Unrecognized override keys for #{worker_name}: #{unknown.join(', ')}. " \
95
+ "Valid keys: #{WORKER_SCOPED_KEYS.join(', ')}"
96
+ end
97
+ private_class_method :validate_worker_override_keys!
98
+
99
+ # Worker-scoped fields (CLI: worker_mode only; all configurable via YAML)
100
+ attr_reader :worker_mode, :backpressure_delay, :max_jobs, :request_timeout,
101
+ :buffer, :buffer_throttle, :job_timeout, :backoff
102
+
103
+ # Process-wide fields
104
+ attr_reader :log_format, :worker_name, :cluster_address
105
+
106
+ def initialize(worker_mode: nil, backpressure_delay: nil, max_jobs: nil, # rubocop:disable Metrics/AbcSize,Metrics/ParameterLists
107
+ request_timeout: nil, buffer: nil, buffer_throttle: nil,
108
+ job_timeout: nil, backoff: nil,
109
+ log_format: nil, worker_name: nil, cluster_address: nil,
110
+ workers: {})
111
+ @worker_mode = coerce_symbol!(worker_mode, VALID_WORKER_MODES, "worker mode") if worker_mode
112
+ @backpressure_delay = backpressure_delay
113
+ @max_jobs = max_jobs
114
+ @request_timeout = request_timeout
115
+ @buffer = buffer
116
+ @buffer_throttle = buffer_throttle
117
+ @job_timeout = job_timeout
118
+ @backoff = backoff
119
+ @log_format = coerce_symbol!(log_format, VALID_LOG_FORMATS, "log format") if log_format
120
+ @worker_name = worker_name
121
+ @cluster_address = cluster_address
122
+ @workers = workers.each_with_object({}) do |(name, overrides), validated|
123
+ if overrides[:worker_mode]
124
+ mode = coerce_symbol!(overrides[:worker_mode], VALID_WORKER_MODES, "worker mode")
125
+ overrides = overrides.merge(worker_mode: mode)
126
+ end
127
+ validated[name.to_s] = overrides
128
+ end
129
+ end
130
+
131
+ # Resolves configuration for a specific worker class through the full
132
+ # precedence chain. Returns a new RuntimeConfig with all values populated.
133
+ #
134
+ # Worker-scoped fields resolve through 4 levels:
135
+ # 1. Per-worker RuntimeConfig override (highest priority)
136
+ # 2. Global RuntimeConfig value
137
+ # 3. Worker DSL (worker_class.configuration)
138
+ # 4. Gem default (lowest priority)
139
+ #
140
+ # Process-wide fields resolve through 2 levels:
141
+ # 1. Global RuntimeConfig value
142
+ # 2. Gem default
143
+ def resolve_for(worker_class) # rubocop:disable Metrics/AbcSize
144
+ wo = @workers[worker_class.name] || {}
145
+ dsl = worker_class.configuration
146
+
147
+ # rubocop:disable Layout/HashAlignment, Layout/LineLength
148
+ self.class.new(
149
+ worker_mode: first_non_nil(wo[:worker_mode], @worker_mode, dsl.worker_mode, Busybee.default_worker_mode),
150
+ backpressure_delay: first_non_nil(wo[:backpressure_delay], @backpressure_delay, dsl.backpressure_delay, Busybee.default_backpressure_delay),
151
+ max_jobs: first_non_nil(wo[:max_jobs], @max_jobs, dsl.polling_config[:max_jobs], Busybee.default_max_jobs),
152
+ request_timeout: first_non_nil(wo[:request_timeout], @request_timeout, dsl.polling_config[:request_timeout], Busybee.default_job_request_timeout),
153
+ buffer: first_non_nil(wo[:buffer], @buffer, dsl.streaming_config[:buffer], Busybee.default_buffer),
154
+ buffer_throttle: first_non_nil(wo[:buffer_throttle], @buffer_throttle, dsl.streaming_config[:buffer_throttle], Busybee.default_buffer_throttle),
155
+ job_timeout: first_non_nil(wo[:job_timeout], @job_timeout, dsl.job_timeout, Busybee.default_job_lock_timeout),
156
+ backoff: first_non_nil(wo[:backoff], @backoff, dsl.backoff, Busybee.default_fail_job_backoff),
157
+ log_format: first_non_nil(@log_format, Busybee.log_format),
158
+ worker_name: first_non_nil(@worker_name, Busybee.worker_name),
159
+ cluster_address: first_non_nil(@cluster_address, Busybee.cluster_address)
160
+ )
161
+ # rubocop:enable Layout/HashAlignment, Layout/LineLength
162
+ end
163
+
164
+ # Convenience method for runner consumption. Returns polling-relevant fields
165
+ # as a hash matching the shape expected by client.with_each_job.
166
+ def polling_options
167
+ { max_jobs: @max_jobs, request_timeout: @request_timeout, job_timeout: @job_timeout }
168
+ end
169
+
170
+ private
171
+
172
+ def first_non_nil(*values)
173
+ values.find { |v| !v.nil? }
174
+ end
175
+
176
+ def coerce_symbol!(value, valid_set, label)
177
+ sym = value.to_s.to_sym
178
+ return sym if valid_set.include?(sym)
179
+
180
+ raise ArgumentError,
181
+ "Invalid #{label}: #{value.inspect}. Valid: #{valid_set.map(&:inspect).join(', ')}"
182
+ end
183
+ end
184
+ end
@@ -142,6 +142,19 @@ module Busybee
142
142
  self
143
143
  end
144
144
 
145
+ # Update the job's timeout.
146
+ #
147
+ # @param timeout [Integer] new timeout in milliseconds
148
+ # @return [self]
149
+ def update_timeout(timeout)
150
+ request = Busybee::GRPC::UpdateJobTimeoutRequest.new(
151
+ jobKey: key,
152
+ timeout: timeout.to_i
153
+ )
154
+ client.update_job_timeout(request)
155
+ self
156
+ end
157
+
145
158
  private
146
159
 
147
160
  def stringify_keys(hash)
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "busybee/job"
4
+ require "busybee/serialization"
5
+
6
+ module Busybee
7
+ module Testing
8
+ module Helpers
9
+ # Builds test jobs and executes workers without a Zeebe connection.
10
+ #
11
+ # Included into Busybee::Testing::Helpers, which is auto-included in all
12
+ # RSpec examples when busybee/testing is required.
13
+ #
14
+ # @example Happy path
15
+ # result = execute_worker(
16
+ # ProcessOrderWorker,
17
+ # variables: { order_id: order.id }
18
+ # )
19
+ # expect(result).to eq(status: "processed")
20
+ #
21
+ # @example Inspect job status after failure
22
+ # job = build_test_job(variables: { order_id: 999 })
23
+ # expect {
24
+ # execute_worker(ProcessOrderWorker, job: job)
25
+ # }.to raise_error(ActiveRecord::RecordNotFound)
26
+ # expect(job).to be_failed
27
+ #
28
+ module Execution
29
+ # Build a test job backed by a stub client.
30
+ #
31
+ # The returned Job behaves like a real job — variables and headers are
32
+ # parsed, status tracking works, and all client operations (complete!,
33
+ # fail!, throw_bpmn_error!, update_retries, update_timeout) succeed
34
+ # silently through a stub client.
35
+ #
36
+ # @param type [String] job type (defaults to "test")
37
+ # @param variables [Hash] process variables
38
+ # @param headers [Hash] custom headers
39
+ # @param bpmn_process_id [String] BPMN process ID
40
+ # @param retries [Integer] retry count
41
+ # @return [Busybee::Job]
42
+ def build_test_job(type: "test", variables: {}, headers: {},
43
+ bpmn_process_id: "test-process", retries: 3)
44
+ client = stub_client
45
+ raw_job = stub_raw_job(
46
+ type: type,
47
+ variables: variables,
48
+ headers: headers,
49
+ bpmn_process_id: bpmn_process_id,
50
+ retries: retries
51
+ )
52
+ Busybee::Job.new(raw_job, client: client)
53
+ end
54
+
55
+ # Execute a worker's full lifecycle against a test job.
56
+ #
57
+ # Runs the real Worker.perform_job — input validation, perform, output
58
+ # validation, auto-complete/auto-fail all execute as in production. The
59
+ # only difference: errors are re-raised after handle_failure completes,
60
+ # so you can use +expect { }.to raise_error+ alongside +expect(job).to be_failed+.
61
+ #
62
+ # @overload execute_worker(worker_class, variables: {}, headers: {},
63
+ # bpmn_process_id: "test-process", retries: 3)
64
+ # Build a test job from keyword arguments and execute.
65
+ # @param worker_class [Class<Busybee::Worker>] the worker class to test
66
+ # @param variables [Hash] process variables
67
+ # @param headers [Hash] custom headers
68
+ # @param bpmn_process_id [String] BPMN process ID
69
+ # @param retries [Integer] retry count
70
+ # @return [Object] the return value of the worker's +perform+ method
71
+ #
72
+ # @overload execute_worker(worker_class, job:)
73
+ # Execute with a pre-built test job (from build_test_job).
74
+ # @param worker_class [Class<Busybee::Worker>] the worker class to test
75
+ # @param job [Busybee::Job] a pre-built test job
76
+ # @return [Object] the return value of the worker's +perform+ method
77
+ #
78
+ def execute_worker(worker_class, job: nil, # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
79
+ variables: {}, headers: {},
80
+ bpmn_process_id: "test-process", retries: 3)
81
+ if job
82
+ unless variables.empty? && headers.empty? &&
83
+ bpmn_process_id == "test-process" && retries == 3
84
+ raise ArgumentError,
85
+ "Cannot pass job: together with variables:, headers:, " \
86
+ "bpmn_process_id:, or retries:. Use build_test_job to " \
87
+ "pre-configure the job, or pass keyword arguments — not both."
88
+ end
89
+ else
90
+ job = build_test_job(
91
+ type: worker_class.job_type,
92
+ variables: variables, headers: headers,
93
+ bpmn_process_id: bpmn_process_id, retries: retries
94
+ )
95
+ end
96
+
97
+ # Wrap handle_failure to re-raise after production logic runs.
98
+ # This lets tests assert both error class AND job status.
99
+ allow(worker_class).to(
100
+ receive(:handle_failure).and_wrap_original do |m, *args|
101
+ m.call(*args).tap { raise args[1] }
102
+ end
103
+ )
104
+
105
+ worker_class.perform_job(job)
106
+ end
107
+
108
+ private
109
+
110
+ def stub_client
111
+ instance_double(
112
+ Busybee::Client,
113
+ complete_job: true,
114
+ fail_job: true,
115
+ throw_bpmn_error: true,
116
+ update_job_retries: true,
117
+ update_job_timeout: true
118
+ )
119
+ end
120
+
121
+ def stub_raw_job(type:, variables:, headers:, bpmn_process_id:, retries:)
122
+ # Plain double because protobuf generates field accessors dynamically
123
+ # via descriptors, which instance_double can't verify against.
124
+ double(
125
+ "Busybee::GRPC::ActivatedJob",
126
+ key: rand(100_000..999_999),
127
+ type: type,
128
+ processInstanceKey: rand(100_000..999_999),
129
+ bpmnProcessId: bpmn_process_id,
130
+ retries: retries,
131
+ deadline: (Time.now.to_i + 300) * 1000,
132
+ variables: Busybee::Serialization.to_json(variables),
133
+ customHeaders: Busybee::Serialization.to_json(headers)
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -43,7 +43,7 @@ module Busybee
43
43
  def activate_jobs_raw(type, max_jobs:, timeout: nil)
44
44
  worker = "#{type}-#{SecureRandom.hex(4)}"
45
45
 
46
- request_timeout = timeout || Busybee::Testing.activate_request_timeout
46
+ request_timeout = timeout || Busybee.default_job_request_timeout
47
47
  request_timeout_ms = if request_timeout.is_a?(ActiveSupport::Duration)
48
48
  request_timeout.in_milliseconds.to_i
49
49
  else
@@ -8,6 +8,7 @@ require "busybee/grpc"
8
8
  require "busybee/serialization"
9
9
  require "busybee/testing/activated_job"
10
10
  require "busybee/testing/helpers/support"
11
+ require "busybee/testing/helpers/execution"
11
12
 
12
13
  module Busybee
13
14
  module Testing
@@ -17,6 +18,7 @@ module Busybee
17
18
  # RSpec helper methods for testing BPMN workflows against Zeebe.
18
19
  module Helpers
19
20
  extend Support
21
+ include Execution
20
22
 
21
23
  # Deploy a BPMN process file to Zeebe.
22
24
  #
@@ -151,7 +153,7 @@ module Busybee
151
153
  # Activate a single job of the given type.
152
154
  #
153
155
  # @param type [String] job type
154
- # @param timeout [Integer, nil] request timeout in milliseconds (defaults to Busybee::Testing.activate_request_timeout)
156
+ # @param timeout [Integer, nil] request timeout in milliseconds (defaults to Busybee.default_job_request_timeout)
155
157
  # @return [ActivatedJob]
156
158
  # @raise [NoJobAvailable] if no job is available
157
159
  def activate_job(type, timeout: nil)
@@ -165,7 +167,7 @@ module Busybee
165
167
  #
166
168
  # @param type [String] job type
167
169
  # @param max_jobs [Integer] maximum number of jobs to activate
168
- # @param timeout [Integer, nil] request timeout in milliseconds (defaults to Busybee::Testing.activate_request_timeout)
170
+ # @param timeout [Integer, nil] request timeout in milliseconds (defaults to Busybee.default_job_request_timeout)
169
171
  # @return [Enumerator<ActivatedJob>]
170
172
  def activate_jobs(type, max_jobs:, timeout: nil)
171
173
  Enumerator.new do |yielder|
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/expectations"
4
+
5
+ # Asserts that a worker completes the job successfully.
6
+ #
7
+ # Checks that the job ends in +:complete+ status. Optionally checks
8
+ # the return value of +perform+ for expected variables via +with_vars+.
9
+ #
10
+ # @example Basic usage
11
+ # job = build_test_job(variables: { order_id: 1 })
12
+ # expect(MyWorker).to complete_job(job)
13
+ #
14
+ # @example With expected return variables
15
+ # expect(MyWorker).to complete_job(job).with_vars(status: "done")
16
+ #
17
+ RSpec::Matchers.define :complete_job do |job|
18
+ match do |worker_class|
19
+ @result = execute_worker(worker_class, job: job)
20
+ return false unless job.complete?
21
+
22
+ if @expected_vars
23
+ if @result.is_a?(Hash)
24
+ values_match?(@expected_vars, @result)
25
+ else
26
+ @expected_vars == {}
27
+ end
28
+ else
29
+ true
30
+ end
31
+ rescue StandardError => e
32
+ @raised_error = e
33
+ false
34
+ end
35
+
36
+ chain :with_vars do |expected|
37
+ @expected_vars = expected
38
+ end
39
+
40
+ chain :with_no_vars do
41
+ @expected_vars = {}
42
+ end
43
+
44
+ failure_message do
45
+ if @raised_error
46
+ "expected #{actual} to complete the job, but it raised " \
47
+ "#{@raised_error.class}: #{@raised_error.message}"
48
+ elsif !job.complete?
49
+ "expected job to be complete, but was #{job.status}"
50
+ else
51
+ "expected result to match #{@expected_vars.inspect}, " \
52
+ "got #{@result.inspect}"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/expectations"
4
+
5
+ # Asserts that a worker fails the job with an optional error match.
6
+ #
7
+ # Accepts the same argument forms as RSpec's +raise_error+:
8
+ # with_error(ErrorClass)
9
+ # with_error(ErrorClass, "exact message")
10
+ # with_error(ErrorClass, /message pattern/)
11
+ # with_error("exact message")
12
+ # with_error(/message pattern/)
13
+ #
14
+ # @example Basic usage
15
+ # job = build_test_job(variables: { order_id: 999 })
16
+ # expect(MyWorker).to fail_job(job)
17
+ #
18
+ # @example With error class
19
+ # expect(MyWorker).to fail_job(job).with_error(ActiveRecord::RecordNotFound)
20
+ #
21
+ # @example With error class and message
22
+ # expect(MyWorker).to fail_job(job).with_error(ArgumentError, /invalid/)
23
+ #
24
+ RSpec::Matchers.define :fail_job do |job|
25
+ match do |worker_class|
26
+ execute_worker(worker_class, job: job)
27
+ @no_error = true
28
+ false
29
+ rescue StandardError => e
30
+ @raised_error = e
31
+ job.failed? && error_matches?(e)
32
+ end
33
+
34
+ chain :with_error do |expected_error_or_message, expected_message = nil|
35
+ case expected_error_or_message
36
+ when String, Regexp
37
+ @expected_error = StandardError
38
+ @expected_message = expected_error_or_message
39
+ else
40
+ @expected_error = expected_error_or_message
41
+ @expected_message = expected_message
42
+ end
43
+ end
44
+
45
+ def error_matches?(error)
46
+ return true unless @expected_error
47
+
48
+ class_matches = @expected_error === error # rubocop:disable Style/CaseEquality
49
+ return false unless class_matches
50
+ return true unless @expected_message
51
+
52
+ case @expected_message
53
+ when Regexp then error.message.match?(@expected_message)
54
+ else error.message == @expected_message.to_s
55
+ end
56
+ end
57
+
58
+ failure_message do
59
+ if @no_error
60
+ "expected #{actual} to fail the job, but it completed successfully"
61
+ elsif !job.failed?
62
+ "expected job to be failed, but was #{job.status}"
63
+ else
64
+ "expected error matching #{expected_description}, " \
65
+ "got #{@raised_error.class}: #{@raised_error.message}"
66
+ end
67
+ end
68
+
69
+ def expected_description
70
+ parts = []
71
+ parts << @expected_error.inspect if @expected_error
72
+ parts << @expected_message.inspect if @expected_message
73
+ parts.join(" with message ")
74
+ end
75
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/expectations"
4
+
5
+ # Asserts that a worker throws a BPMN error on the job.
6
+ #
7
+ # Captures the error code and message passed to the stub client's
8
+ # +throw_bpmn_error+ call for optional verification via +with_code+.
9
+ #
10
+ # @example Basic usage
11
+ # job = build_test_job(variables: { order_id: 999 })
12
+ # expect(MyWorker).to throw_bpmn_error_on(job)
13
+ #
14
+ # @example With error code
15
+ # expect(MyWorker).to throw_bpmn_error_on(job).with_code(:not_found)
16
+ #
17
+ # @example With error code and message
18
+ # expect(MyWorker).to throw_bpmn_error_on(job).with_code(:not_found, message: /missing/)
19
+ #
20
+ RSpec::Matchers.define :throw_bpmn_error_on do |job|
21
+ match do |worker_class|
22
+ client = job.instance_variable_get(:@client)
23
+ allow(client).to receive(:throw_bpmn_error) do |_key, code, message:|
24
+ @actual_code = code
25
+ @actual_message = message
26
+ end
27
+
28
+ execute_worker(worker_class, job: job)
29
+ return false unless job.error?
30
+
31
+ code_matches? && message_matches?
32
+ rescue StandardError => e
33
+ @raised_error = e
34
+ false
35
+ end
36
+
37
+ chain :with_code do |expected_code, opts = {}|
38
+ @expected_code = case expected_code
39
+ when Symbol then expected_code.to_s.upcase
40
+ when Class then expected_code.name.gsub("::", "_").underscore.upcase
41
+ else expected_code
42
+ end
43
+ @expected_message = opts[:message]
44
+ end
45
+
46
+ def code_matches?
47
+ return true unless @expected_code
48
+
49
+ values_match?(@expected_code, @actual_code)
50
+ end
51
+
52
+ def message_matches?
53
+ return true unless @expected_message
54
+
55
+ values_match?(@expected_message, @actual_message)
56
+ end
57
+
58
+ failure_message do
59
+ if @raised_error
60
+ "expected #{actual} to throw a BPMN error, but it raised " \
61
+ "#{@raised_error.class}: #{@raised_error.message}"
62
+ elsif !job.error?
63
+ "expected job to be error, but was #{job.status}"
64
+ elsif @expected_code && !code_matches?
65
+ "expected BPMN error code #{@expected_code.inspect}, " \
66
+ "got #{@actual_code.inspect}"
67
+ else
68
+ "expected BPMN error message #{@expected_message.inspect}, " \
69
+ "got #{@actual_message.inspect}"
70
+ end
71
+ end
72
+ end
@@ -3,25 +3,8 @@
3
3
  require "busybee/grpc"
4
4
 
5
5
  module Busybee
6
- # Testing support for BPMN workflows with RSpec.
7
- #
8
- # @example Configuration
9
- # Busybee::Testing.configure do |config|
10
- # config.activate_request_timeout = 2000
11
- # end
12
- #
6
+ # Testing support for BPMN workflows and workers with RSpec.
13
7
  module Testing
14
- class << self
15
- attr_writer :activate_request_timeout
16
-
17
- def configure
18
- yield self
19
- end
20
-
21
- def activate_request_timeout
22
- @activate_request_timeout || 1000
23
- end
24
- end
25
8
  end
26
9
  end
27
10
 
@@ -33,6 +16,9 @@ if defined?(RSpec)
33
16
  require "busybee/testing/matchers/have_received_headers"
34
17
  require "busybee/testing/matchers/have_activated"
35
18
  require "busybee/testing/matchers/have_available_jobs"
19
+ require "busybee/testing/matchers/fail_job"
20
+ require "busybee/testing/matchers/complete_job"
21
+ require "busybee/testing/matchers/throw_bpmn_error_on"
36
22
 
37
23
  RSpec.configure do |config|
38
24
  config.include Busybee::Testing::Helpers
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Busybee
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end