freddy 1.4.1 → 1.4.2
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/.rubocop.yml +49 -0
- data/.travis.yml +1 -1
- data/Gemfile +4 -2
- data/Rakefile +6 -4
- data/freddy.gemspec +21 -21
- data/lib/freddy.rb +7 -8
- data/lib/freddy/adapters.rb +2 -0
- data/lib/freddy/adapters/bunny_adapter.rb +7 -7
- data/lib/freddy/adapters/march_hare_adapter.rb +6 -4
- data/lib/freddy/consumers.rb +2 -0
- data/lib/freddy/consumers/respond_to_consumer.rb +14 -17
- data/lib/freddy/consumers/response_consumer.rb +4 -2
- data/lib/freddy/consumers/tap_into_consumer.rb +11 -14
- data/lib/freddy/delivery.rb +2 -0
- data/lib/freddy/error_response.rb +2 -0
- data/lib/freddy/invalid_request_error.rb +2 -0
- data/lib/freddy/message_handler.rb +3 -1
- data/lib/freddy/message_handler_adapaters.rb +2 -0
- data/lib/freddy/payload.rb +5 -3
- data/lib/freddy/producers.rb +2 -0
- data/lib/freddy/producers/reply_producer.rb +6 -6
- data/lib/freddy/producers/send_and_forget_producer.rb +8 -8
- data/lib/freddy/producers/send_and_wait_response_producer.rb +39 -31
- data/lib/freddy/request_manager.rb +7 -4
- data/lib/freddy/responder_handler.rb +2 -0
- data/lib/freddy/sync_response_container.rb +4 -4
- data/lib/freddy/timeout_error.rb +2 -0
- data/lib/freddy/trace_carrier.rb +4 -2
- data/spec/freddy/consumers/respond_to_consumer_spec.rb +1 -1
- data/spec/freddy/error_response_spec.rb +9 -9
- data/spec/freddy/freddy_spec.rb +38 -38
- data/spec/freddy/message_handler_spec.rb +2 -2
- data/spec/freddy/payload_spec.rb +5 -5
- data/spec/freddy/responder_handler_spec.rb +1 -1
- data/spec/freddy/sync_response_container_spec.rb +8 -8
- data/spec/freddy/trace_carrier_spec.rb +5 -5
- data/spec/integration/concurrency_spec.rb +9 -9
- data/spec/integration/reply_spec.rb +4 -4
- data/spec/integration/tap_into_with_group_spec.rb +2 -2
- data/spec/integration/tracing_spec.rb +19 -19
- data/spec/spec_helper.rb +6 -6
- metadata +9 -8
data/lib/freddy/producers.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Freddy
|
2
4
|
module Producers
|
3
5
|
class ReplyProducer
|
4
|
-
CONTENT_TYPE = 'application/json'
|
6
|
+
CONTENT_TYPE = 'application/json'
|
5
7
|
|
6
8
|
def initialize(channel, logger)
|
7
9
|
@logger = logger
|
@@ -9,11 +11,9 @@ class Freddy
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def produce(destination, payload, properties)
|
12
|
-
OpenTracing.active_span
|
13
|
-
|
14
|
-
|
15
|
-
payload: payload
|
16
|
-
)
|
14
|
+
if (span = OpenTracing.active_span)
|
15
|
+
span.set_tag('message_bus.destination', destination)
|
16
|
+
end
|
17
17
|
|
18
18
|
properties = properties.merge(
|
19
19
|
routing_key: destination,
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Freddy
|
2
4
|
module Producers
|
3
5
|
class SendAndForgetProducer
|
4
|
-
CONTENT_TYPE = 'application/json'
|
6
|
+
CONTENT_TYPE = 'application/json'
|
5
7
|
|
6
8
|
def initialize(channel, logger)
|
7
9
|
@logger = logger
|
@@ -11,13 +13,11 @@ class Freddy
|
|
11
13
|
|
12
14
|
def produce(destination, payload, properties)
|
13
15
|
span = OpenTracing.start_span("freddy:notify:#{destination}",
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
)
|
20
|
-
span.log_kv event: 'Sending message', payload: payload
|
16
|
+
tags: {
|
17
|
+
'message_bus.destination' => destination,
|
18
|
+
'component' => 'freddy',
|
19
|
+
'span.kind' => 'producer' # Message Bus
|
20
|
+
})
|
21
21
|
|
22
22
|
properties = properties.merge(
|
23
23
|
routing_key: destination,
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Freddy
|
2
4
|
module Producers
|
3
5
|
class SendAndWaitResponseProducer
|
4
|
-
CONTENT_TYPE = 'application/json'
|
6
|
+
CONTENT_TYPE = 'application/json'
|
5
7
|
|
6
8
|
def initialize(channel, logger)
|
7
9
|
@logger = logger
|
@@ -16,36 +18,36 @@ class Freddy
|
|
16
18
|
@request_manager.no_route(correlation_id)
|
17
19
|
end
|
18
20
|
|
19
|
-
@response_queue = @channel.queue(
|
21
|
+
@response_queue = @channel.queue('', exclusive: true)
|
20
22
|
|
21
23
|
@response_consumer = Consumers::ResponseConsumer.new(@logger)
|
22
24
|
@response_consumer.consume(@channel, @response_queue, &method(:handle_response))
|
23
25
|
end
|
24
26
|
|
25
27
|
def produce(destination, payload, timeout_in_seconds:, delete_on_timeout:, **properties)
|
26
|
-
span = OpenTracing.start_span("freddy:request:#{destination}",
|
27
|
-
tags: {
|
28
|
-
'component': 'freddy',
|
29
|
-
'span.kind': 'client', # RPC
|
30
|
-
'payload.type': payload[:type] || 'unknown'
|
31
|
-
}
|
32
|
-
)
|
33
|
-
|
34
28
|
correlation_id = SecureRandom.uuid
|
35
29
|
|
30
|
+
span = OpenTracing.start_span("freddy:request:#{destination}",
|
31
|
+
tags: {
|
32
|
+
'component' => 'freddy',
|
33
|
+
'span.kind' => 'client', # RPC
|
34
|
+
'payload.type' => payload[:type] || 'unknown',
|
35
|
+
'message_bus.destination' => destination,
|
36
|
+
'message_bus.response_queue' => @response_queue.name,
|
37
|
+
'message_bus.correlation_id' => correlation_id,
|
38
|
+
'freddy.timeout_in_seconds' => timeout_in_seconds
|
39
|
+
})
|
40
|
+
|
36
41
|
container = SyncResponseContainer.new(
|
37
42
|
on_timeout(correlation_id, destination, timeout_in_seconds, span)
|
38
43
|
)
|
39
44
|
|
40
45
|
@request_manager.store(correlation_id,
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
)
|
46
|
+
callback: container,
|
47
|
+
span: span,
|
48
|
+
destination: destination)
|
45
49
|
|
46
|
-
if delete_on_timeout
|
47
|
-
properties[:expiration] = (timeout_in_seconds * 1000).to_i
|
48
|
-
end
|
50
|
+
properties[:expiration] = (timeout_in_seconds * 1000).to_i if delete_on_timeout
|
49
51
|
|
50
52
|
properties = properties.merge(
|
51
53
|
routing_key: destination, content_type: CONTENT_TYPE,
|
@@ -55,13 +57,6 @@ class Freddy
|
|
55
57
|
OpenTracing.global_tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, TraceCarrier.new(properties))
|
56
58
|
json_payload = Payload.dump(payload)
|
57
59
|
|
58
|
-
span.log_kv(
|
59
|
-
event: 'Publishing request',
|
60
|
-
payload: payload,
|
61
|
-
response_queue: @response_queue.name,
|
62
|
-
correlation_id: correlation_id
|
63
|
-
)
|
64
|
-
|
65
60
|
# Connection adapters handle thread safety for #publish themselves. No
|
66
61
|
# need to lock these.
|
67
62
|
@topic_exchange.publish json_payload, properties.dup
|
@@ -73,11 +68,11 @@ class Freddy
|
|
73
68
|
def handle_response(delivery)
|
74
69
|
correlation_id = delivery.correlation_id
|
75
70
|
|
76
|
-
if request = @request_manager.delete(correlation_id)
|
71
|
+
if (request = @request_manager.delete(correlation_id))
|
77
72
|
process_response(request, delivery)
|
78
73
|
else
|
79
74
|
message = "Got rpc response for correlation_id #{correlation_id} "\
|
80
|
-
|
75
|
+
'but there is no requester'
|
81
76
|
@logger.warn message
|
82
77
|
end
|
83
78
|
end
|
@@ -86,17 +81,30 @@ class Freddy
|
|
86
81
|
@logger.debug "Got response for request to #{request[:destination]} "\
|
87
82
|
"with correlation_id #{delivery.correlation_id}"
|
88
83
|
request[:callback].call(delivery.payload, delivery)
|
84
|
+
rescue InvalidRequestError => e
|
85
|
+
request[:span].set_tag('error', true)
|
86
|
+
request[:span].log_kv(
|
87
|
+
event: 'invalid request',
|
88
|
+
message: e.message,
|
89
|
+
'error.object': e
|
90
|
+
)
|
91
|
+
raise e
|
89
92
|
ensure
|
90
|
-
request[:
|
93
|
+
request[:span].finish
|
91
94
|
end
|
92
95
|
|
93
|
-
def on_timeout(correlation_id, destination, timeout_in_seconds,
|
94
|
-
|
96
|
+
def on_timeout(correlation_id, destination, timeout_in_seconds, span)
|
97
|
+
proc do
|
95
98
|
@logger.warn "Request timed out waiting response from #{destination}"\
|
96
|
-
", correlation id #{correlation_id}"
|
99
|
+
", correlation id #{correlation_id}, timeout #{timeout_in_seconds}s"
|
97
100
|
|
98
101
|
@request_manager.delete(correlation_id)
|
99
|
-
|
102
|
+
span.set_tag('error', true)
|
103
|
+
span.log_kv(
|
104
|
+
event: 'timed out',
|
105
|
+
message: "Timed out waiting response from #{destination}"
|
106
|
+
)
|
107
|
+
span.finish
|
100
108
|
end
|
101
109
|
end
|
102
110
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Freddy
|
2
4
|
class RequestManager
|
3
5
|
def initialize(logger)
|
@@ -6,10 +8,11 @@ class Freddy
|
|
6
8
|
end
|
7
9
|
|
8
10
|
def no_route(correlation_id)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
request = @requests[correlation_id]
|
12
|
+
return unless request
|
13
|
+
|
14
|
+
delete(correlation_id)
|
15
|
+
request[:callback].call({ error: 'Specified queue does not exist' }, nil)
|
13
16
|
end
|
14
17
|
|
15
18
|
def store(correlation_id, opts)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'timeout'
|
2
4
|
|
3
5
|
class Freddy
|
@@ -9,9 +11,7 @@ class Freddy
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def call(response, delivery)
|
12
|
-
if response.nil?
|
13
|
-
raise StandardError, 'unexpected nil value for response'
|
14
|
-
end
|
14
|
+
raise StandardError, 'unexpected nil value for response' if response.nil?
|
15
15
|
|
16
16
|
@response = response
|
17
17
|
@delivery = delivery
|
@@ -28,7 +28,7 @@ class Freddy
|
|
28
28
|
message: 'Timed out waiting for response'
|
29
29
|
)
|
30
30
|
elsif !@delivery || @delivery.type == 'error'
|
31
|
-
raise InvalidRequestError
|
31
|
+
raise InvalidRequestError, @response
|
32
32
|
else
|
33
33
|
@response
|
34
34
|
end
|
data/lib/freddy/timeout_error.rb
CHANGED
data/lib/freddy/trace_carrier.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Freddy
|
2
4
|
# Carrier for rabbitmq following OpenTracing API
|
3
5
|
# See https://github.com/opentracing/opentracing-ruby/blob/master/lib/opentracing/carrier.rb
|
@@ -18,8 +20,8 @@ class Freddy
|
|
18
20
|
def each(&block)
|
19
21
|
Hash[
|
20
22
|
(@properties.headers || {})
|
21
|
-
|
22
|
-
|
23
|
+
.select { |key, _| key =~ /^x-trace/ }
|
24
|
+
.map { |key, value| [key.sub(/x-trace-/, ''), value] }
|
23
25
|
].each(&block)
|
24
26
|
end
|
25
27
|
end
|
@@ -12,7 +12,7 @@ describe Freddy::Consumers::RespondToConsumer do
|
|
12
12
|
|
13
13
|
let(:connection) { Freddy::Adapters.determine.connect(config) }
|
14
14
|
let(:destination) { random_destination }
|
15
|
-
let(:payload) { {pay: 'load'} }
|
15
|
+
let(:payload) { { pay: 'load' } }
|
16
16
|
let(:msg_handler_adapter_factory) { double(for: msg_handler_adapter) }
|
17
17
|
let(:msg_handler_adapter) { Freddy::MessageHandlerAdapters::NoOpHandler.new }
|
18
18
|
let(:prefetch_buffer_size) { 2 }
|
@@ -4,55 +4,55 @@ describe Freddy::ErrorResponse do
|
|
4
4
|
subject(:error) { described_class.new(input) }
|
5
5
|
|
6
6
|
context 'with an error type' do
|
7
|
-
let(:input) { {error: 'SomeError'} }
|
7
|
+
let(:input) { { error: 'SomeError' } }
|
8
8
|
|
9
9
|
describe '#response' do
|
10
10
|
subject { error.response }
|
11
11
|
|
12
|
-
it {
|
12
|
+
it { is_expected.to eq(input) }
|
13
13
|
end
|
14
14
|
|
15
15
|
describe '#message' do
|
16
16
|
subject { error.message }
|
17
17
|
|
18
18
|
it 'uses error type as a message' do
|
19
|
-
|
19
|
+
is_expected.to eq('SomeError')
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
context 'with an error type and message' do
|
25
|
-
let(:input) { {error: 'SomeError', message: 'extra info'} }
|
25
|
+
let(:input) { { error: 'SomeError', message: 'extra info' } }
|
26
26
|
|
27
27
|
describe '#response' do
|
28
28
|
subject { error.response }
|
29
29
|
|
30
|
-
it {
|
30
|
+
it { is_expected.to eq(input) }
|
31
31
|
end
|
32
32
|
|
33
33
|
describe '#message' do
|
34
34
|
subject { error.message }
|
35
35
|
|
36
36
|
it 'uses error type as a message' do
|
37
|
-
|
37
|
+
is_expected.to eq('SomeError: extra info')
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
context 'without an error type' do
|
43
|
-
let(:input) { {something: 'else'} }
|
43
|
+
let(:input) { { something: 'else' } }
|
44
44
|
|
45
45
|
describe '#response' do
|
46
46
|
subject { error.response }
|
47
47
|
|
48
|
-
it {
|
48
|
+
it { is_expected.to eq(input) }
|
49
49
|
end
|
50
50
|
|
51
51
|
describe '#message' do
|
52
52
|
subject { error.message }
|
53
53
|
|
54
54
|
it 'uses default error message as a message' do
|
55
|
-
|
55
|
+
is_expected.to eq('Use #response to get the error response')
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
data/spec/freddy/freddy_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe Freddy do
|
|
5
5
|
|
6
6
|
let(:destination) { random_destination }
|
7
7
|
let(:destination2) { random_destination }
|
8
|
-
let(:payload) { {pay: 'load'} }
|
8
|
+
let(:payload) { { pay: 'load' } }
|
9
9
|
|
10
10
|
after { freddy.close }
|
11
11
|
|
@@ -18,7 +18,7 @@ describe Freddy do
|
|
18
18
|
it 'removes the message from the queue after the timeout' do
|
19
19
|
# Assume that there already is a queue. Otherwise will get an early
|
20
20
|
# return.
|
21
|
-
consumer = freddy.respond_to(destination) {
|
21
|
+
consumer = freddy.respond_to(destination) {}
|
22
22
|
consumer.shutdown
|
23
23
|
|
24
24
|
freddy.deliver(destination, {}, timeout: 0.1)
|
@@ -36,7 +36,7 @@ describe Freddy do
|
|
36
36
|
it 'keeps the message in the queue' do
|
37
37
|
# Assume that there already is a queue. Otherwise will get an early
|
38
38
|
# return.
|
39
|
-
consumer = freddy.respond_to(destination) {
|
39
|
+
consumer = freddy.respond_to(destination) {}
|
40
40
|
consumer.shutdown
|
41
41
|
|
42
42
|
freddy.deliver(destination, {})
|
@@ -53,50 +53,50 @@ describe Freddy do
|
|
53
53
|
|
54
54
|
context 'when making a synchronized request' do
|
55
55
|
it 'returns response as soon as possible' do
|
56
|
-
respond_to { |
|
57
|
-
response = freddy.deliver_with_response(destination,
|
56
|
+
respond_to { |_payload, msg_handler| msg_handler.success(res: 'yey') }
|
57
|
+
response = freddy.deliver_with_response(destination, a: 'b')
|
58
58
|
|
59
59
|
expect(response).to eq(res: 'yey')
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'raises an error if the message was errored' do
|
63
|
-
respond_to { |
|
63
|
+
respond_to { |_payload, msg_handler| msg_handler.error(error: 'not today') }
|
64
64
|
|
65
|
-
expect
|
65
|
+
expect do
|
66
66
|
freddy.deliver_with_response(destination, payload)
|
67
|
-
|
67
|
+
end.to raise_error(Freddy::InvalidRequestError) { |error|
|
68
68
|
expect(error.response).to eq(error: 'not today')
|
69
69
|
}
|
70
70
|
end
|
71
71
|
|
72
72
|
it 'responds to the correct requester' do
|
73
|
-
respond_to { |
|
73
|
+
respond_to { |_payload, msg_handler| msg_handler.success(res: 'yey') }
|
74
74
|
|
75
75
|
response = freddy.deliver_with_response(destination, payload)
|
76
76
|
expect(response).to eq(res: 'yey')
|
77
77
|
|
78
|
-
expect
|
78
|
+
expect do
|
79
79
|
freddy.deliver_with_response(destination2, payload)
|
80
|
-
|
80
|
+
end.to raise_error(Freddy::InvalidRequestError)
|
81
81
|
end
|
82
82
|
|
83
83
|
context 'when queue does not exist' do
|
84
84
|
it 'gives a no route error' do
|
85
|
-
expect
|
86
|
-
freddy.deliver_with_response(destination, {a: 'b'}, timeout: 1)
|
87
|
-
|
85
|
+
expect do
|
86
|
+
freddy.deliver_with_response(destination, { a: 'b' }, timeout: 1)
|
87
|
+
end.to raise_error(Freddy::InvalidRequestError) { |error|
|
88
88
|
expect(error.response).to eq(error: 'Specified queue does not exist')
|
89
89
|
}
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
context '
|
93
|
+
context 'when timeout' do
|
94
94
|
it 'gives timeout error' do
|
95
|
-
respond_to { |
|
95
|
+
respond_to { |_payload, _msg_handler| sleep 0.2 }
|
96
96
|
|
97
|
-
expect
|
98
|
-
freddy.deliver_with_response(destination, {a: 'b'}, timeout: 0.1)
|
99
|
-
|
97
|
+
expect do
|
98
|
+
freddy.deliver_with_response(destination, { a: 'b' }, timeout: 0.1)
|
99
|
+
end.to raise_error(Freddy::TimeoutError) { |error|
|
100
100
|
expect(error.response).to eq(error: 'RequestTimeout', message: 'Timed out waiting for response')
|
101
101
|
}
|
102
102
|
end
|
@@ -105,12 +105,12 @@ describe Freddy do
|
|
105
105
|
it 'removes the message from the queue' do
|
106
106
|
# Assume that there already is a queue. Otherwise will get an early
|
107
107
|
# return.
|
108
|
-
consumer = freddy.respond_to(destination) {
|
108
|
+
consumer = freddy.respond_to(destination) {}
|
109
109
|
consumer.shutdown
|
110
110
|
|
111
|
-
expect
|
111
|
+
expect do
|
112
112
|
freddy.deliver_with_response(destination, {}, timeout: 0.1)
|
113
|
-
|
113
|
+
end.to raise_error(Freddy::TimeoutError)
|
114
114
|
default_sleep # to ensure everything is properly cleaned
|
115
115
|
|
116
116
|
processed_after_timeout = false
|
@@ -125,12 +125,12 @@ describe Freddy do
|
|
125
125
|
it 'removes the message from the queue' do
|
126
126
|
# Assume that there already is a queue. Otherwise will get an early
|
127
127
|
# return.
|
128
|
-
consumer = freddy.respond_to(destination) {
|
128
|
+
consumer = freddy.respond_to(destination) {}
|
129
129
|
consumer.shutdown
|
130
130
|
|
131
|
-
expect
|
131
|
+
expect do
|
132
132
|
freddy.deliver_with_response(destination, {}, timeout: 0.1, delete_on_timeout: false)
|
133
|
-
|
133
|
+
end.to raise_error(Freddy::TimeoutError)
|
134
134
|
default_sleep # to ensure everything is properly cleaned
|
135
135
|
|
136
136
|
processed_after_timeout = false
|
@@ -149,7 +149,7 @@ describe Freddy do
|
|
149
149
|
end
|
150
150
|
|
151
151
|
it 'receives messages' do
|
152
|
-
tap {|msg| @tapped_message = msg }
|
152
|
+
tap { |msg| @tapped_message = msg }
|
153
153
|
deliver
|
154
154
|
|
155
155
|
wait_for { @tapped_message }
|
@@ -157,13 +157,13 @@ describe Freddy do
|
|
157
157
|
end
|
158
158
|
|
159
159
|
it 'has the destination' do
|
160
|
-
tap
|
160
|
+
tap 'somebody.*.love' do |_message, destination|
|
161
161
|
@destination = destination
|
162
162
|
end
|
163
|
-
deliver
|
163
|
+
deliver 'somebody.to.love'
|
164
164
|
|
165
165
|
wait_for { @destination }
|
166
|
-
expect(@destination).to eq(
|
166
|
+
expect(@destination).to eq('somebody.to.love')
|
167
167
|
end
|
168
168
|
|
169
169
|
it "doesn't consume the message" do
|
@@ -178,28 +178,28 @@ describe Freddy do
|
|
178
178
|
expect(@message_received).to be(true)
|
179
179
|
end
|
180
180
|
|
181
|
-
it
|
182
|
-
tap(
|
181
|
+
it 'allows * wildcard' do
|
182
|
+
tap('somebody.*.love') { @tapped = true }
|
183
183
|
|
184
|
-
deliver
|
184
|
+
deliver 'somebody.to.love'
|
185
185
|
|
186
186
|
wait_for { @tapped }
|
187
187
|
expect(@tapped).to be(true)
|
188
188
|
end
|
189
189
|
|
190
|
-
it
|
191
|
-
tap(
|
190
|
+
it '* matches only one word' do
|
191
|
+
tap('somebody.*.love') { @tapped = true }
|
192
192
|
|
193
|
-
deliver
|
193
|
+
deliver 'somebody.not.to.love'
|
194
194
|
|
195
195
|
default_sleep
|
196
196
|
expect(@tapped).to be_falsy
|
197
197
|
end
|
198
198
|
|
199
|
-
it
|
200
|
-
tap(
|
199
|
+
it 'allows # wildcard' do
|
200
|
+
tap('i.#.free') { @tapped = true }
|
201
201
|
|
202
|
-
deliver
|
202
|
+
deliver 'i.want.to.break.free'
|
203
203
|
|
204
204
|
wait_for { @tapped }
|
205
205
|
expect(@tapped).to be(true)
|