dexkit 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +63 -0
- data/README.md +116 -3
- data/guides/llm/EVENT.md +300 -0
- data/guides/llm/FORM.md +520 -0
- data/guides/llm/OPERATION.md +1 -1
- data/lib/dex/concern.rb +10 -0
- data/lib/dex/event/bus.rb +98 -0
- data/lib/dex/event/execution_state.rb +17 -0
- data/lib/dex/event/handler.rb +77 -0
- data/lib/dex/event/metadata.rb +54 -0
- data/lib/dex/event/processor.rb +61 -0
- data/lib/dex/event/suppression.rb +49 -0
- data/lib/dex/event/trace.rb +56 -0
- data/lib/dex/event.rb +87 -0
- data/lib/dex/event_test_helpers/assertions.rb +70 -0
- data/lib/dex/event_test_helpers.rb +88 -0
- data/lib/dex/form/nesting.rb +189 -0
- data/lib/dex/form/uniqueness_validator.rb +86 -0
- data/lib/dex/form.rb +142 -0
- data/lib/dex/operation/async_proxy.rb +30 -36
- data/lib/dex/operation/async_wrapper.rb +3 -19
- data/lib/dex/operation/callback_wrapper.rb +11 -15
- data/lib/dex/operation/jobs.rb +8 -14
- data/lib/dex/operation/lock_wrapper.rb +2 -11
- data/lib/dex/operation/pipeline.rb +5 -5
- data/lib/dex/operation/record_wrapper.rb +10 -38
- data/lib/dex/operation/rescue_wrapper.rb +1 -3
- data/lib/dex/operation/result_wrapper.rb +7 -14
- data/lib/dex/operation/settings.rb +10 -3
- data/lib/dex/operation/transaction_wrapper.rb +7 -20
- data/lib/dex/operation.rb +57 -105
- data/lib/dex/{operation/props_setup.rb → props_setup.rb} +12 -15
- data/lib/dex/test_helpers.rb +3 -1
- data/lib/dex/type_coercion.rb +96 -0
- data/lib/dex/version.rb +1 -1
- data/lib/dexkit.rb +15 -1
- metadata +49 -4
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Event
|
|
5
|
+
class Handler
|
|
6
|
+
attr_reader :event
|
|
7
|
+
|
|
8
|
+
def self.on(*event_classes)
|
|
9
|
+
event_classes.each do |ec|
|
|
10
|
+
Event.validate_event_class!(ec)
|
|
11
|
+
Bus.subscribe(ec, self)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.retries(count, **opts)
|
|
16
|
+
raise ArgumentError, "retries count must be a positive Integer" unless count.is_a?(Integer) && count > 0
|
|
17
|
+
|
|
18
|
+
if opts.key?(:wait)
|
|
19
|
+
wait = opts[:wait]
|
|
20
|
+
unless wait.is_a?(Numeric) || wait.is_a?(Proc)
|
|
21
|
+
raise ArgumentError, "wait: must be Numeric or Proc"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@_event_handler_retries = { count: count, **opts }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self._event_handler_retry_config
|
|
29
|
+
if defined?(@_event_handler_retries)
|
|
30
|
+
@_event_handler_retries
|
|
31
|
+
elsif superclass.respond_to?(:_event_handler_retry_config)
|
|
32
|
+
superclass._event_handler_retry_config
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self._event_handle(event)
|
|
37
|
+
instance = new
|
|
38
|
+
instance.instance_variable_set(:@event, event)
|
|
39
|
+
instance.perform
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self._event_handle_from_payload(event_class_name, payload, metadata_hash)
|
|
43
|
+
event_class = Object.const_get(event_class_name)
|
|
44
|
+
event = _event_reconstruct(event_class, payload, metadata_hash)
|
|
45
|
+
_event_handle(event)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def _event_reconstruct(event_class, payload, metadata_hash)
|
|
52
|
+
coerced = event_class.send(:_coerce_serialized_hash, payload)
|
|
53
|
+
instance = event_class.allocate
|
|
54
|
+
|
|
55
|
+
event_class.literal_properties.each do |prop|
|
|
56
|
+
instance.instance_variable_set(:"@#{prop.name}", coerced[prop.name])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
metadata = Event::Metadata.new(
|
|
60
|
+
id: metadata_hash["id"],
|
|
61
|
+
timestamp: Time.parse(metadata_hash["timestamp"]),
|
|
62
|
+
trace_id: metadata_hash["trace_id"],
|
|
63
|
+
caused_by_id: metadata_hash["caused_by_id"],
|
|
64
|
+
context: metadata_hash["context"]
|
|
65
|
+
)
|
|
66
|
+
instance.instance_variable_set(:@metadata, metadata)
|
|
67
|
+
instance.freeze
|
|
68
|
+
instance
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def perform
|
|
73
|
+
raise NotImplementedError, "#{self.class.name} must implement #perform"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Dex
|
|
6
|
+
class Event
|
|
7
|
+
class Metadata
|
|
8
|
+
attr_reader :id, :timestamp, :trace_id, :caused_by_id, :context
|
|
9
|
+
|
|
10
|
+
def initialize(id:, timestamp:, trace_id:, caused_by_id:, context:)
|
|
11
|
+
@id = id
|
|
12
|
+
@timestamp = timestamp
|
|
13
|
+
@trace_id = trace_id
|
|
14
|
+
@caused_by_id = caused_by_id
|
|
15
|
+
@context = context
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.build(caused_by_id: nil)
|
|
20
|
+
id = SecureRandom.uuid
|
|
21
|
+
trace_id = Trace.current_trace_id || id
|
|
22
|
+
caused = caused_by_id || Trace.current_event_id
|
|
23
|
+
|
|
24
|
+
ctx = if Dex.configuration.event_context
|
|
25
|
+
begin
|
|
26
|
+
Dex.configuration.event_context.call
|
|
27
|
+
rescue => e
|
|
28
|
+
Event._warn("event_context failed: #{e.message}")
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
new(
|
|
34
|
+
id: id,
|
|
35
|
+
timestamp: Time.now.utc,
|
|
36
|
+
trace_id: trace_id,
|
|
37
|
+
caused_by_id: caused,
|
|
38
|
+
context: ctx
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def as_json
|
|
43
|
+
h = {
|
|
44
|
+
"id" => @id,
|
|
45
|
+
"timestamp" => @timestamp.iso8601(6),
|
|
46
|
+
"trace_id" => @trace_id
|
|
47
|
+
}
|
|
48
|
+
h["caused_by_id"] = @caused_by_id if @caused_by_id
|
|
49
|
+
h["context"] = @context if @context
|
|
50
|
+
h
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Event
|
|
5
|
+
# Lazy-loaded ActiveJob processor (same pattern as Operation::DirectJob)
|
|
6
|
+
def self.const_missing(name)
|
|
7
|
+
return super unless name == :Processor && defined?(ActiveJob::Base)
|
|
8
|
+
|
|
9
|
+
const_set(:Processor, Class.new(ActiveJob::Base) do
|
|
10
|
+
def perform(handler_class:, event_class:, payload:, metadata:, trace: nil, context: nil, attempt_number: 1)
|
|
11
|
+
restore_context(context)
|
|
12
|
+
|
|
13
|
+
handler = Object.const_get(handler_class)
|
|
14
|
+
retry_config = handler._event_handler_retry_config
|
|
15
|
+
|
|
16
|
+
Dex::Event::Trace.restore(trace) do
|
|
17
|
+
handler._event_handle_from_payload(event_class, payload, metadata)
|
|
18
|
+
end
|
|
19
|
+
rescue => _e
|
|
20
|
+
if retry_config && attempt_number <= retry_config[:count]
|
|
21
|
+
delay = compute_delay(retry_config, attempt_number)
|
|
22
|
+
self.class.set(wait: delay).perform_later(
|
|
23
|
+
handler_class: handler_class,
|
|
24
|
+
event_class: event_class,
|
|
25
|
+
payload: payload,
|
|
26
|
+
metadata: metadata,
|
|
27
|
+
trace: trace,
|
|
28
|
+
context: context,
|
|
29
|
+
attempt_number: attempt_number + 1
|
|
30
|
+
)
|
|
31
|
+
else
|
|
32
|
+
raise
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def restore_context(context)
|
|
39
|
+
return unless context
|
|
40
|
+
|
|
41
|
+
restorer = Dex.configuration.restore_event_context
|
|
42
|
+
return unless restorer
|
|
43
|
+
|
|
44
|
+
restorer.call(context)
|
|
45
|
+
rescue => e
|
|
46
|
+
Dex::Event._warn("restore_event_context failed: #{e.message}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def compute_delay(config, attempt)
|
|
50
|
+
wait = config[:wait]
|
|
51
|
+
case wait
|
|
52
|
+
when Numeric then wait
|
|
53
|
+
when Proc then wait.call(attempt)
|
|
54
|
+
else
|
|
55
|
+
2**(attempt - 1) # exponential backoff
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Event
|
|
5
|
+
module Suppression
|
|
6
|
+
SUPPRESSED_KEY = :_dex_event_suppressed
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
include ExecutionState
|
|
10
|
+
|
|
11
|
+
def suppress(*classes, &block)
|
|
12
|
+
previous = _suppressed_set
|
|
13
|
+
new_set = previous.dup
|
|
14
|
+
if classes.empty?
|
|
15
|
+
new_set << :all
|
|
16
|
+
else
|
|
17
|
+
classes.each do |klass|
|
|
18
|
+
Event.validate_event_class!(klass)
|
|
19
|
+
new_set << klass
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
_set_suppressed(new_set)
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
_set_suppressed(previous)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def suppressed?(event_class)
|
|
29
|
+
set = _suppressed_set
|
|
30
|
+
set.include?(:all) || set.any? { |k| k != :all && event_class <= k }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def clear!
|
|
34
|
+
_execution_state[SUPPRESSED_KEY] = Set.new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def _suppressed_set
|
|
40
|
+
_execution_state[SUPPRESSED_KEY] ||= Set.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def _set_suppressed(set)
|
|
44
|
+
_execution_state[SUPPRESSED_KEY] = set
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Event
|
|
5
|
+
module Trace
|
|
6
|
+
STACK_KEY = :_dex_event_trace_stack
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
include ExecutionState
|
|
10
|
+
|
|
11
|
+
def with_event(event, &block)
|
|
12
|
+
stack = _stack
|
|
13
|
+
stack.push(event.trace_frame)
|
|
14
|
+
yield
|
|
15
|
+
ensure
|
|
16
|
+
stack.pop
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def current_event_id
|
|
20
|
+
_stack.last&.dig(:id)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def current_trace_id
|
|
24
|
+
_stack.last&.dig(:trace_id)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def dump
|
|
28
|
+
frame = _stack.last
|
|
29
|
+
return nil unless frame
|
|
30
|
+
|
|
31
|
+
{ id: frame[:id], trace_id: frame[:trace_id] }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def restore(data, &block)
|
|
35
|
+
return yield unless data
|
|
36
|
+
|
|
37
|
+
stack = _stack
|
|
38
|
+
stack.push(data)
|
|
39
|
+
yield
|
|
40
|
+
ensure
|
|
41
|
+
stack.pop if data
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def clear!
|
|
45
|
+
_execution_state[STACK_KEY] = []
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def _stack
|
|
51
|
+
_execution_state[STACK_KEY] ||= []
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/dex/event.rb
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
# Modules loaded before class body (no reference to Dex::Event needed)
|
|
6
|
+
require_relative "event/execution_state"
|
|
7
|
+
require_relative "event/metadata"
|
|
8
|
+
require_relative "event/trace"
|
|
9
|
+
require_relative "event/suppression"
|
|
10
|
+
|
|
11
|
+
module Dex
|
|
12
|
+
class Event
|
|
13
|
+
RESERVED_PROP_NAMES = %i[
|
|
14
|
+
id timestamp trace_id caused_by_id caused_by
|
|
15
|
+
context publish metadata sync
|
|
16
|
+
].to_set.freeze
|
|
17
|
+
|
|
18
|
+
include PropsSetup
|
|
19
|
+
include TypeCoercion
|
|
20
|
+
|
|
21
|
+
def self._warn(message)
|
|
22
|
+
Dex.warn("Event: #{message}")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.validate_event_class!(klass)
|
|
26
|
+
return if klass.is_a?(Class) && klass < Dex::Event
|
|
27
|
+
|
|
28
|
+
raise ArgumentError, "#{klass.inspect} is not a Dex::Event subclass"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# --- Instance ---
|
|
32
|
+
|
|
33
|
+
attr_reader :metadata
|
|
34
|
+
|
|
35
|
+
def after_initialize
|
|
36
|
+
@metadata = Metadata.build
|
|
37
|
+
freeze
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Metadata delegates
|
|
41
|
+
def id = metadata.id
|
|
42
|
+
def timestamp = metadata.timestamp
|
|
43
|
+
def trace_id = metadata.trace_id
|
|
44
|
+
def caused_by_id = metadata.caused_by_id
|
|
45
|
+
def context = metadata.context
|
|
46
|
+
def trace_frame = { id: id, trace_id: trace_id }
|
|
47
|
+
|
|
48
|
+
# Publishing
|
|
49
|
+
def publish(sync: false)
|
|
50
|
+
Bus.publish(self, sync: sync)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.publish(sync: false, caused_by: nil, **kwargs)
|
|
54
|
+
if caused_by
|
|
55
|
+
Trace.with_event(caused_by) do
|
|
56
|
+
new(**kwargs).publish(sync: sync)
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
new(**kwargs).publish(sync: sync)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Tracing
|
|
64
|
+
def trace(&block)
|
|
65
|
+
Trace.with_event(self, &block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Suppression
|
|
69
|
+
def self.suppress(*classes, &block)
|
|
70
|
+
Suppression.suppress(*classes, &block)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Serialization
|
|
74
|
+
def as_json
|
|
75
|
+
{
|
|
76
|
+
"type" => self.class.name,
|
|
77
|
+
"payload" => _props_as_json,
|
|
78
|
+
"metadata" => metadata.as_json
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Classes loaded after Event is defined (they reference Dex::Event)
|
|
85
|
+
require_relative "event/bus"
|
|
86
|
+
require_relative "event/handler"
|
|
87
|
+
require_relative "event/processor"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Event
|
|
5
|
+
module TestHelpers
|
|
6
|
+
def assert_event_published(event_class, msg: nil, **expected_props)
|
|
7
|
+
matching = _dex_find_published_events(event_class, expected_props)
|
|
8
|
+
assert matching.any?,
|
|
9
|
+
msg || _dex_event_published_failure_message(event_class, expected_props)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def refute_event_published(event_class = nil, msg: nil, **expected_props)
|
|
13
|
+
if event_class.nil?
|
|
14
|
+
assert _dex_published_events.empty?,
|
|
15
|
+
msg || "Expected no events published, but #{_dex_published_events.size} were:\n#{_dex_event_list}"
|
|
16
|
+
else
|
|
17
|
+
matching = _dex_find_published_events(event_class, expected_props)
|
|
18
|
+
assert matching.empty?,
|
|
19
|
+
msg || "Expected no #{event_class.name || event_class} events published, but found #{matching.size}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def assert_event_count(event_class, count, msg: nil)
|
|
24
|
+
matching = _dex_find_published_events(event_class, {})
|
|
25
|
+
assert_equal count, matching.size,
|
|
26
|
+
msg || "Expected #{count} #{event_class.name || event_class} events, got #{matching.size}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def assert_event_trace(parent, child, msg: nil)
|
|
30
|
+
assert_equal parent.id, child.caused_by_id,
|
|
31
|
+
msg || "Expected child event to be caused by parent (caused_by_id mismatch)"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def assert_same_trace(*events, msg: nil)
|
|
35
|
+
trace_ids = events.map(&:trace_id).uniq
|
|
36
|
+
assert_equal 1, trace_ids.size,
|
|
37
|
+
msg || "Expected all events to share the same trace_id, got: #{trace_ids.inspect}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def _dex_find_published_events(event_class, expected_props)
|
|
43
|
+
_dex_published_events.select do |event|
|
|
44
|
+
next false unless event.is_a?(event_class)
|
|
45
|
+
|
|
46
|
+
expected_props.all? do |key, value|
|
|
47
|
+
event.respond_to?(key) && event.public_send(key) == value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def _dex_event_published_failure_message(event_class, expected_props)
|
|
53
|
+
name = event_class.name || event_class.to_s
|
|
54
|
+
if _dex_published_events.empty?
|
|
55
|
+
"Expected #{name} to be published, but no events were published"
|
|
56
|
+
else
|
|
57
|
+
msg = "Expected #{name} to be published"
|
|
58
|
+
msg += " with #{expected_props.inspect}" unless expected_props.empty?
|
|
59
|
+
msg + ", but only found:\n#{_dex_event_list}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def _dex_event_list
|
|
64
|
+
_dex_published_events.map.with_index do |event, i|
|
|
65
|
+
" #{i + 1}. #{event.class.name || event.class}"
|
|
66
|
+
end.join("\n")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dex
|
|
4
|
+
class Event
|
|
5
|
+
module EventTestWrapper
|
|
6
|
+
CAPTURING_KEY = :_dex_event_capturing
|
|
7
|
+
PUBLISHED_KEY = :_dex_event_published
|
|
8
|
+
|
|
9
|
+
@_installed = false
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
include ExecutionState
|
|
13
|
+
|
|
14
|
+
def install!
|
|
15
|
+
return if @_installed
|
|
16
|
+
|
|
17
|
+
Dex::Event::Bus.singleton_class.prepend(BusInterceptor)
|
|
18
|
+
@_installed = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def installed?
|
|
22
|
+
@_installed
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def capturing?
|
|
26
|
+
(_execution_state[CAPTURING_KEY] || 0) > 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def begin_capture!
|
|
30
|
+
_execution_state[CAPTURING_KEY] = (_execution_state[CAPTURING_KEY] || 0) + 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def end_capture!
|
|
34
|
+
depth = (_execution_state[CAPTURING_KEY] || 0) - 1
|
|
35
|
+
_execution_state[CAPTURING_KEY] = [depth, 0].max
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def published_events
|
|
39
|
+
_execution_state[PUBLISHED_KEY] ||= []
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def clear_published!
|
|
43
|
+
_execution_state[PUBLISHED_KEY] = []
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module BusInterceptor
|
|
48
|
+
def publish(event, sync:)
|
|
49
|
+
if Dex::Event::EventTestWrapper.capturing?
|
|
50
|
+
return if Dex::Event::Suppression.suppressed?(event.class)
|
|
51
|
+
|
|
52
|
+
Dex::Event::EventTestWrapper.published_events << event
|
|
53
|
+
else
|
|
54
|
+
super(event, sync: true)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module TestHelpers
|
|
61
|
+
def self.included(base)
|
|
62
|
+
EventTestWrapper.install!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def setup
|
|
66
|
+
super
|
|
67
|
+
EventTestWrapper.clear_published!
|
|
68
|
+
Dex::Event::Trace.clear!
|
|
69
|
+
Dex::Event::Suppression.clear!
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def capture_events
|
|
73
|
+
EventTestWrapper.begin_capture!
|
|
74
|
+
yield
|
|
75
|
+
ensure
|
|
76
|
+
EventTestWrapper.end_capture!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def _dex_published_events
|
|
82
|
+
EventTestWrapper.published_events
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
require_relative "event_test_helpers/assertions"
|