freddy 1.4.1 → 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|