rabbit_feed 1.0.2 → 2.0.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/.rspec +1 -0
- data/DEVELOPING.md +14 -2
- data/Gemfile.lock +27 -26
- data/README.md +17 -11
- data/example/non_rails_app/Gemfile.lock +18 -17
- data/example/non_rails_app/spec/lib/non_rails_app/event_handler_spec.rb +1 -1
- data/example/non_rails_app/spec/lib/non_rails_app/event_routing_spec.rb +4 -2
- data/example/rails_app/Gemfile.lock +34 -33
- data/example/rails_app/spec/event_routing_spec.rb +3 -3
- data/lib/rabbit_feed/configuration.rb +25 -11
- data/lib/rabbit_feed/connection_concern.rb +4 -13
- data/lib/rabbit_feed/consumer_connection.rb +0 -6
- data/lib/rabbit_feed/event.rb +58 -18
- data/lib/rabbit_feed/event_definitions.rb +18 -6
- data/lib/rabbit_feed/producer.rb +12 -11
- data/lib/rabbit_feed/producer_connection.rb +1 -1
- data/lib/rabbit_feed/testing_support/rspec_matchers/publish_event.rb +2 -8
- data/lib/rabbit_feed/testing_support/test_rabbit_feed_consumer.rb +3 -3
- data/lib/rabbit_feed/version.rb +1 -1
- data/lib/rabbit_feed.rb +1 -0
- data/rabbit_feed.gemspec +2 -2
- data/spec/features/step_definitions/connectivity_steps.rb +1 -1
- data/spec/fixtures/configuration.yml +0 -1
- data/spec/lib/rabbit_feed/configuration_spec.rb +59 -12
- data/spec/lib/rabbit_feed/connection_concern_spec.rb +11 -0
- data/spec/lib/rabbit_feed/event_definitions_spec.rb +25 -11
- data/spec/lib/rabbit_feed/event_routing_spec.rb +7 -7
- data/spec/lib/rabbit_feed/event_spec.rb +119 -11
- data/spec/lib/rabbit_feed/producer_spec.rb +14 -0
- data/spec/lib/rabbit_feed/testing_support/testing_helper_spec.rb +6 -3
- metadata +6 -6
data/lib/rabbit_feed/event.rb
CHANGED
@@ -2,42 +2,82 @@ module RabbitFeed
|
|
2
2
|
class Event
|
3
3
|
include ActiveModel::Validations
|
4
4
|
|
5
|
-
|
6
|
-
validates_presence_of :schema, :payload
|
5
|
+
SCHEMA_VERSION = '2.0.0'
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
attr_reader :schema, :payload, :metadata
|
8
|
+
validates :metadata, presence: true
|
9
|
+
validates :payload, length: { minimum: 0, allow_nil: false, message: 'can\'t be nil' }
|
10
|
+
validate :required_metadata
|
11
|
+
|
12
|
+
def initialize metadata, payload={}, schema=nil
|
13
|
+
@schema = schema
|
14
|
+
@payload = payload.with_indifferent_access if payload
|
15
|
+
@metadata = metadata.with_indifferent_access if metadata
|
11
16
|
validate!
|
12
17
|
end
|
13
18
|
|
14
19
|
def serialize
|
15
20
|
buffer = StringIO.new
|
16
21
|
writer = Avro::DataFile::Writer.new buffer, (Avro::IO::DatumWriter.new schema), schema
|
17
|
-
writer << payload
|
22
|
+
writer << { 'metadata' => metadata, 'payload' => payload }
|
18
23
|
writer.close
|
19
24
|
buffer.string
|
20
25
|
end
|
21
26
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
reader.close
|
30
|
-
Event.new datum_reader.readers_schema, payload
|
27
|
+
def application
|
28
|
+
metadata[:application]
|
29
|
+
end
|
30
|
+
|
31
|
+
def name
|
32
|
+
metadata[:name]
|
31
33
|
end
|
32
34
|
|
33
|
-
def
|
34
|
-
|
35
|
+
def created_at_utc
|
36
|
+
(Time.iso8601 metadata[:created_at_utc]) if metadata[:created_at_utc].present?
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
|
41
|
+
def deserialize serialized_event
|
42
|
+
datum_reader = Avro::IO::DatumReader.new
|
43
|
+
reader = Avro::DataFile::Reader.new (StringIO.new serialized_event), datum_reader
|
44
|
+
event_hash = nil
|
45
|
+
reader.each do |datum|
|
46
|
+
event_hash = datum
|
47
|
+
end
|
48
|
+
reader.close
|
49
|
+
if (version_1? event_hash)
|
50
|
+
new_from_version_1 event_hash, datum_reader.readers_schema
|
51
|
+
else
|
52
|
+
new event_hash['metadata'], event_hash['payload'], datum_reader.readers_schema
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def version_1? event_hash
|
59
|
+
%w(metadata payload).none?{|key| event_hash.has_key? key}
|
60
|
+
end
|
61
|
+
|
62
|
+
def new_from_version_1 metadata_and_payload, schema
|
63
|
+
metadata = {}
|
64
|
+
%w(application name host version environment created_at_utc).each do |field|
|
65
|
+
metadata[field] = metadata_and_payload.delete field
|
66
|
+
end
|
67
|
+
new metadata, metadata_and_payload, schema
|
68
|
+
end
|
35
69
|
end
|
36
70
|
|
37
71
|
private
|
38
72
|
|
39
73
|
def validate!
|
40
|
-
raise Error.new errors.messages if invalid?
|
74
|
+
raise Error.new "Invalid event: #{errors.messages}" if invalid?
|
75
|
+
end
|
76
|
+
|
77
|
+
def required_metadata
|
78
|
+
if metadata
|
79
|
+
errors.add(:metadata, 'name field is required') if metadata[:name].blank?
|
80
|
+
end
|
41
81
|
end
|
42
82
|
end
|
43
83
|
end
|
@@ -51,19 +51,31 @@ module RabbitFeed
|
|
51
51
|
@definition = block.call if block.present?
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
55
|
-
(
|
54
|
+
def payload_schema
|
55
|
+
{ name: "#{name}_payload", type: 'record', fields: fields.map(&:schema) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def metadata_schema
|
59
|
+
{ name: 'event_metadata', type: 'record', fields: [
|
56
60
|
(Field.new 'application', 'string', 'The name of the application that created the event'),
|
57
61
|
(Field.new 'host', 'string', 'The hostname of the server on which the event was created'),
|
58
62
|
(Field.new 'environment', 'string', 'The environment in which the event was created'),
|
59
|
-
(Field.new 'version', 'string', 'The version of the event'),
|
63
|
+
(Field.new 'version', 'string', 'The version of the event payload'),
|
64
|
+
(Field.new 'schema_version', 'string', 'The version of the event schema'),
|
60
65
|
(Field.new 'name', 'string', 'The name of the event'),
|
61
|
-
(Field.new 'created_at_utc', 'string', 'The UTC time that the event was created')
|
62
|
-
]
|
66
|
+
(Field.new 'created_at_utc', 'string', 'The UTC time that the event was created'),
|
67
|
+
].map(&:schema) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def event_schema
|
71
|
+
[
|
72
|
+
{ name: 'payload', type: payload_schema, doc: 'The event payload (defined by the source system)' },
|
73
|
+
{ name: 'metadata', type: metadata_schema, doc: 'The event metadata (defined by rabbit feed)' },
|
74
|
+
]
|
63
75
|
end
|
64
76
|
|
65
77
|
def schema
|
66
|
-
@schema ||= (Avro::Schema.parse ({ name: name, type: 'record', doc: definition, fields:
|
78
|
+
@schema ||= (Avro::Schema.parse ({ name: name, type: 'record', doc: definition, fields: event_schema }.to_json))
|
67
79
|
end
|
68
80
|
|
69
81
|
def validate!
|
data/lib/rabbit_feed/producer.rb
CHANGED
@@ -8,23 +8,24 @@ module RabbitFeed
|
|
8
8
|
raise (Error.new 'Unable to publish event. No event definitions set.') unless event_definitions.present?
|
9
9
|
event_definition = event_definitions[name] or raise (Error.new "definition for event: #{name} not found")
|
10
10
|
timestamp = Time.now.utc
|
11
|
-
|
12
|
-
event = Event.new event_definition.schema
|
11
|
+
metadata = (metadata event_definition.version, name, timestamp)
|
12
|
+
event = Event.new metadata, payload, event_definition.schema
|
13
13
|
ProducerConnection.publish event.serialize, (options name, timestamp)
|
14
14
|
event
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
def metadata version, name, timestamp
|
20
|
+
{
|
21
|
+
'application' => RabbitFeed.configuration.application,
|
22
|
+
'host' => Socket.gethostname,
|
23
|
+
'environment' => RabbitFeed.environment,
|
24
|
+
'created_at_utc' => timestamp.iso8601(6),
|
25
|
+
'version' => version,
|
26
|
+
'name' => name,
|
27
|
+
'schema_version' => Event::SCHEMA_VERSION,
|
28
|
+
}
|
28
29
|
end
|
29
30
|
|
30
31
|
def routing_key event_name
|
@@ -28,7 +28,7 @@ module RabbitFeed
|
|
28
28
|
|
29
29
|
with_expected_payload = negative_expectation
|
30
30
|
if received_expected_event && !with_expected_payload
|
31
|
-
actual_payload =
|
31
|
+
actual_payload = actual_event.payload
|
32
32
|
with_expected_payload = expected_payload.nil? || actual_payload == expected_payload
|
33
33
|
end
|
34
34
|
|
@@ -61,16 +61,10 @@ module RabbitFeed
|
|
61
61
|
|
62
62
|
private
|
63
63
|
|
64
|
-
def strip_defaults_from payload
|
65
|
-
payload.reject do |key, value|
|
66
|
-
['application', 'host', 'environment', 'created_at_utc', 'version', 'name'].include? key
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
64
|
def received_events_message
|
71
65
|
if TestingSupport.published_events.any?
|
72
66
|
TestingSupport.published_events.map do |received_event|
|
73
|
-
"#{received_event.name} with #{
|
67
|
+
"#{received_event.name} with #{received_event.payload}"
|
74
68
|
end
|
75
69
|
else
|
76
70
|
'no events'
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module RabbitFeed
|
2
2
|
module TestingSupport
|
3
3
|
class TestRabbitFeedConsumer
|
4
|
-
|
5
|
-
|
6
|
-
RabbitFeed::Consumer.event_routing.handle_event
|
4
|
+
|
5
|
+
def consume_event event
|
6
|
+
RabbitFeed::Consumer.event_routing.handle_event event
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
data/lib/rabbit_feed/version.rb
CHANGED
data/lib/rabbit_feed.rb
CHANGED
data/rabbit_feed.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
# Gem for interfacing with RabbitMq
|
22
|
-
spec.add_dependency 'bunny', '>= 1.1.9', '< 1.
|
22
|
+
spec.add_dependency 'bunny', '>= 1.1.9', '< 1.8.0'
|
23
23
|
# We use some helpers from ActiveSupport
|
24
24
|
spec.add_dependency 'activesupport', '>= 3.2.0', '< 5.0.0'
|
25
25
|
# We use validations from ActiveModel
|
@@ -31,5 +31,5 @@ Gem::Specification.new do |spec|
|
|
31
31
|
# Schema definitions and serialization for events
|
32
32
|
spec.add_dependency 'avro', '>= 1.5.4', '< 1.8.0'
|
33
33
|
# For stubbing and custom matchers
|
34
|
-
spec.add_development_dependency 'rspec', '>=2.14.0', '< 3.
|
34
|
+
spec.add_development_dependency 'rspec', '>=2.14.0', '< 3.3.0'
|
35
35
|
end
|
@@ -15,6 +15,57 @@ module RabbitFeed
|
|
15
15
|
it { should eq 'test.rabbit_feed' }
|
16
16
|
end
|
17
17
|
|
18
|
+
describe '#connection_options' do
|
19
|
+
subject { (described_class.new options).connection_options }
|
20
|
+
|
21
|
+
context 'when there are no defaults' do
|
22
|
+
let(:options) do
|
23
|
+
{
|
24
|
+
application: 'rabbit_feed',
|
25
|
+
environment: 'test',
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
it { should include(logger: RabbitFeed.log) }
|
30
|
+
it { should include(recover_from_connection_close: true) }
|
31
|
+
it { should include(threaded: true) }
|
32
|
+
it { should_not include(:heartbeat) }
|
33
|
+
it { should_not include(:network_recovery_interval) }
|
34
|
+
it { should_not include(:connect_timeout) }
|
35
|
+
it { should_not include(:host) }
|
36
|
+
it { should_not include(:user) }
|
37
|
+
it { should_not include(:password) }
|
38
|
+
it { should_not include(:port) }
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when there are defaults' do
|
42
|
+
let(:options) do
|
43
|
+
{
|
44
|
+
application: 'rabbit_feed',
|
45
|
+
environment: 'test',
|
46
|
+
heartbeat: 5,
|
47
|
+
connect_timeout: 1,
|
48
|
+
host: 'localhost',
|
49
|
+
user: 'guest',
|
50
|
+
password: 'guest',
|
51
|
+
port: 1234,
|
52
|
+
network_recovery_interval: 1,
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
it { should include(heartbeat: 5) }
|
57
|
+
it { should include(connect_timeout: 1) }
|
58
|
+
it { should include(host: 'localhost') }
|
59
|
+
it { should include(user: 'guest') }
|
60
|
+
it { should include(password: 'guest') }
|
61
|
+
it { should include(port: 1234) }
|
62
|
+
it { should include(network_recovery_interval: 1) }
|
63
|
+
it { should include(logger: RabbitFeed.log) }
|
64
|
+
it { should include(recover_from_connection_close: true) }
|
65
|
+
it { should include(threaded: true) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
18
69
|
describe '.load' do
|
19
70
|
let(:file_path) { 'spec/fixtures/configuration.yml' }
|
20
71
|
let(:environment) { 'test' }
|
@@ -45,7 +96,6 @@ module RabbitFeed
|
|
45
96
|
its(:application) { should eq 'rabbit_feed' }
|
46
97
|
its(:environment) { should eq 'test' }
|
47
98
|
its(:exchange) { should eq 'rabbit_feed_exchange' }
|
48
|
-
its(:pool_size) { should eq 1 }
|
49
99
|
its(:pool_timeout) { should eq 1 }
|
50
100
|
its(:heartbeat) { should eq 60 }
|
51
101
|
its(:connect_timeout) { should eq 1 }
|
@@ -68,16 +118,15 @@ module RabbitFeed
|
|
68
118
|
}
|
69
119
|
end
|
70
120
|
|
71
|
-
its(:host) { should
|
72
|
-
its(:port) { should
|
73
|
-
its(:user) { should
|
74
|
-
its(:password) { should
|
121
|
+
its(:host) { should be_nil }
|
122
|
+
its(:port) { should be_nil }
|
123
|
+
its(:user) { should be_nil }
|
124
|
+
its(:password) { should be_nil }
|
125
|
+
its(:heartbeat) { should be_nil }
|
126
|
+
its(:network_recovery_interval) { should be_nil }
|
75
127
|
its(:exchange) { should eq 'amq.topic' }
|
76
|
-
its(:
|
77
|
-
its(:
|
78
|
-
its(:heartbeat) { should eq 5 }
|
79
|
-
its(:connect_timeout) { should eq 10 }
|
80
|
-
its(:network_recovery_interval) { should eq 1 }
|
128
|
+
its(:pool_timeout) { should eq 120 }
|
129
|
+
its(:connect_timeout) { should be_nil }
|
81
130
|
its(:auto_delete_queue) { should be_falsey }
|
82
131
|
its(:auto_delete_exchange) { should be_falsey }
|
83
132
|
end
|
@@ -92,7 +141,6 @@ module RabbitFeed
|
|
92
141
|
application: 'rabbit_feed',
|
93
142
|
environment: 'test',
|
94
143
|
exchange: 'exchange_name',
|
95
|
-
pool_size: 2,
|
96
144
|
pool_timeout: 6,
|
97
145
|
heartbeat: 3,
|
98
146
|
connect_timeout: 4,
|
@@ -109,7 +157,6 @@ module RabbitFeed
|
|
109
157
|
its(:application) { should eq 'rabbit_feed' }
|
110
158
|
its(:environment) { should eq 'test' }
|
111
159
|
its(:exchange) { should eq 'exchange_name' }
|
112
|
-
its(:pool_size) { should eq 2 }
|
113
160
|
its(:pool_timeout) { should eq 6 }
|
114
161
|
its(:heartbeat) { should eq 3 }
|
115
162
|
its(:connect_timeout) { should eq 4 }
|
@@ -30,6 +30,11 @@ module RabbitFeed
|
|
30
30
|
expect(subject.instance_variable_get(:@connection_pool)).to be_a ConnectionPool
|
31
31
|
end
|
32
32
|
|
33
|
+
it 'creates a connection pool of one connection' do
|
34
|
+
expect(ConnectionPool).to receive(:new).with(hash_including({size: 1})).and_call_original
|
35
|
+
subject.with_connection{|connection| connection }
|
36
|
+
end
|
37
|
+
|
33
38
|
it 'provides an instance of the class' do
|
34
39
|
actual = subject.with_connection{|connection| connection }
|
35
40
|
expect(actual).to be_a subject
|
@@ -97,6 +102,7 @@ module RabbitFeed
|
|
97
102
|
describe '.retry_on_closed_connection' do
|
98
103
|
before do
|
99
104
|
subject.with_connection{|connection| connection }
|
105
|
+
allow(subject).to receive(:sleep).at_least(:once)
|
100
106
|
end
|
101
107
|
|
102
108
|
it_behaves_like 'an operation that retries on exception', :retry_on_closed_connection, Bunny::ConnectionClosedError
|
@@ -111,6 +117,11 @@ module RabbitFeed
|
|
111
117
|
expect { subject.retry_on_closed_connection { raise Bunny::ConnectionClosedError.new 'blah' } }.to raise_error
|
112
118
|
expect(subject.instance_variable_get(:@connection_pool)).to be_nil
|
113
119
|
end
|
120
|
+
|
121
|
+
it 'waits between retries' do
|
122
|
+
expect(subject).to receive(:sleep).with(1).twice
|
123
|
+
begin; subject.retry_on_closed_connection { raise Bunny::ConnectionClosedError.new 'blah' }; rescue; end
|
124
|
+
end
|
114
125
|
end
|
115
126
|
end
|
116
127
|
end
|
@@ -38,17 +38,31 @@ module RabbitFeed
|
|
38
38
|
end
|
39
39
|
|
40
40
|
it { should be_valid }
|
41
|
-
its(:fields)
|
42
|
-
its(:schema)
|
43
|
-
its(:
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
41
|
+
its(:fields) { should_not be_empty }
|
42
|
+
its(:schema) { should be_a Avro::Schema }
|
43
|
+
its(:metadata_schema) { should eq({
|
44
|
+
name: 'event_metadata',
|
45
|
+
type: 'record',
|
46
|
+
fields: [
|
47
|
+
{name: 'application', type: 'string', doc: 'The name of the application that created the event'},
|
48
|
+
{name: 'host', type: 'string', doc: 'The hostname of the server on which the event was created'},
|
49
|
+
{name: 'environment', type: 'string', doc: 'The environment in which the event was created'},
|
50
|
+
{name: 'version', type: 'string', doc: 'The version of the event payload'},
|
51
|
+
{name: 'schema_version', type: 'string', doc: 'The version of the event schema'},
|
52
|
+
{name: 'name', type: 'string', doc: 'The name of the event'},
|
53
|
+
{name: 'created_at_utc', type: 'string', doc: 'The UTC time that the event was created'},
|
54
|
+
]})
|
55
|
+
}
|
56
|
+
its(:payload_schema) { should eq({
|
57
|
+
name: 'event_name_payload',
|
58
|
+
type: 'record',
|
59
|
+
fields: [{name: 'field', type: 'string', doc: 'field definition'}],
|
60
|
+
})
|
61
|
+
}
|
62
|
+
its(:event_schema) { should match([
|
63
|
+
{ name: 'payload', type: an_instance_of(Hash), doc: 'The event payload (defined by the source system)' },
|
64
|
+
{ name: 'metadata', type: an_instance_of(Hash), doc: 'The event metadata (defined by rabbit feed)' },
|
65
|
+
])
|
52
66
|
}
|
53
67
|
|
54
68
|
context 'when the name is nil' do
|
@@ -41,11 +41,11 @@ module RabbitFeed
|
|
41
41
|
|
42
42
|
it 'routes the event to the correct action, preferring named applications' do
|
43
43
|
events = [
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
Event.new({application: 'dummy_1', name: 'event_1'}, {payload: 1}),
|
45
|
+
Event.new({application: 'dummy_1', name: 'event_2'}, {payload: 2}),
|
46
|
+
Event.new({application: 'dummy_1', name: 'event_4'}, {payload: 4}),
|
47
|
+
Event.new({application: 'dummy_2', name: 'event_3'}, {payload: 3}),
|
48
|
+
Event.new({application: 'none', name: 'event_4'}, {payload: 4}),
|
49
49
|
]
|
50
50
|
events.each do |event|
|
51
51
|
(RabbitFeed::Consumer.event_routing.handle_event event).should eq event.payload
|
@@ -54,8 +54,8 @@ module RabbitFeed
|
|
54
54
|
|
55
55
|
it 'raises a routing error when the event cannot be routed' do
|
56
56
|
events = [
|
57
|
-
|
58
|
-
|
57
|
+
Event.new({application: 'dummy_9', name: 'event_1'}, {payload: 1}),
|
58
|
+
Event.new({application: 'dummy_1', name: 'event_9'}, {payload: 3}),
|
59
59
|
]
|
60
60
|
events.each do |event|
|
61
61
|
expect{ RabbitFeed::Consumer.event_routing.handle_event event }.to raise_error RoutingError
|
@@ -2,32 +2,140 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module RabbitFeed
|
4
4
|
describe Event do
|
5
|
-
let(:schema)
|
6
|
-
let(:payload)
|
5
|
+
let(:schema) { double(:schema) }
|
6
|
+
let(:payload) { { 'customer_id' => '123' } }
|
7
|
+
let(:metadata) { { 'name' => 'test_event' } }
|
7
8
|
|
8
|
-
subject { described_class.new
|
9
|
+
subject { described_class.new metadata, payload, schema }
|
9
10
|
|
10
11
|
describe '.new' do
|
11
12
|
|
12
13
|
it { should be_valid }
|
13
|
-
its(:schema)
|
14
|
-
its(:payload)
|
14
|
+
its(:schema) { should eq schema }
|
15
|
+
its(:payload) { should eq({ 'customer_id' => '123' }) }
|
16
|
+
its(:metadata) { should eq({ 'name' => 'test_event' }) }
|
15
17
|
|
16
|
-
context 'when
|
17
|
-
let(:
|
18
|
+
context 'when payload is nil' do
|
19
|
+
let(:payload) {}
|
18
20
|
|
19
21
|
it 'should raise an error' do
|
20
|
-
expect{ subject }.to raise_error
|
22
|
+
expect{ subject }.to raise_error 'Invalid event: {:payload=>["can\'t be nil"]}'
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
|
-
context 'when
|
25
|
-
let(:
|
26
|
+
context 'when metadata is blank' do
|
27
|
+
let(:metadata) {}
|
28
|
+
|
29
|
+
it 'should raise an error' do
|
30
|
+
expect{ subject }.to raise_error 'Invalid event: {:metadata=>["can\'t be blank"]}'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when name is blank' do
|
35
|
+
let(:metadata) {{ 'name' => '' }}
|
26
36
|
|
27
37
|
it 'should raise an error' do
|
28
|
-
expect{ subject }.to raise_error
|
38
|
+
expect{ subject }.to raise_error 'Invalid event: {:metadata=>["name field is required"]}'
|
29
39
|
end
|
30
40
|
end
|
31
41
|
end
|
42
|
+
|
43
|
+
describe '#name' do
|
44
|
+
let(:metadata) { { 'name' => 'test_event' } }
|
45
|
+
|
46
|
+
its(:name) { should eq('test_event') }
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#application' do
|
50
|
+
let(:metadata) { { 'name' => 'test_event', 'application' => 'test_application' } }
|
51
|
+
|
52
|
+
its(:application) { should eq('test_application') }
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#created_at_utc' do
|
56
|
+
|
57
|
+
context 'when the created_at_utc is in the metadata' do
|
58
|
+
let(:metadata) { { 'name' => 'test_event', 'created_at_utc' => '2015-03-02T15:55:19.411299Z' } }
|
59
|
+
|
60
|
+
its(:created_at_utc) { should eq(Time.iso8601('2015-03-02T15:55:19.411299Z')) }
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when the created_at_utc is not in the metadata' do
|
64
|
+
let(:metadata) { { 'name' => 'test_event', 'created_at_utc' => '' } }
|
65
|
+
|
66
|
+
its(:created_at_utc) { should be_nil }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '.deserialize' do
|
71
|
+
subject { described_class.deserialize serialized_event }
|
72
|
+
|
73
|
+
context 'from a post-1.0 event' do
|
74
|
+
let(:schema) do
|
75
|
+
Avro::Schema.parse ({ name: 'post-1.0', type: 'record', fields: [
|
76
|
+
{ name: 'payload', type: {
|
77
|
+
name: 'event_payload', type: 'record', fields: [
|
78
|
+
{ name: 'customer_id', type: 'string' },
|
79
|
+
]
|
80
|
+
},
|
81
|
+
},
|
82
|
+
{ name: 'metadata', type: {
|
83
|
+
name: 'event_metadata', type: 'record', fields: [
|
84
|
+
{ name: 'name', type: 'string' },
|
85
|
+
]
|
86
|
+
},
|
87
|
+
},
|
88
|
+
]}.to_json)
|
89
|
+
end
|
90
|
+
let(:serialized_event) { (described_class.new metadata, payload, schema).serialize }
|
91
|
+
|
92
|
+
its(:schema) { should eq schema }
|
93
|
+
its(:payload) { should eq({ 'customer_id' => '123' }) }
|
94
|
+
its(:metadata) { should eq({ 'name' => 'test_event' }) }
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'from a 1.0 event' do
|
98
|
+
let(:metadata_and_payload) do
|
99
|
+
{
|
100
|
+
'application' => 'rabbit_feed',
|
101
|
+
'name' => 'test_event',
|
102
|
+
'host' => 'localhost',
|
103
|
+
'version' => '1.0.0',
|
104
|
+
'environment' => 'test',
|
105
|
+
'created_at_utc' => '2015-02-22',
|
106
|
+
'customer_id' => '123',
|
107
|
+
}
|
108
|
+
end
|
109
|
+
let(:schema) do
|
110
|
+
Avro::Schema.parse ({ name: '1.0', type: 'record', fields: [
|
111
|
+
{ name: 'customer_id', type: 'string' },
|
112
|
+
{ name: 'application', type: 'string' },
|
113
|
+
{ name: 'name', type: 'string' },
|
114
|
+
{ name: 'host', type: 'string' },
|
115
|
+
{ name: 'version', type: 'string' },
|
116
|
+
{ name: 'environment', type: 'string' },
|
117
|
+
{ name: 'created_at_utc', type: 'string' },
|
118
|
+
]}.to_json)
|
119
|
+
end
|
120
|
+
let(:serialized_event) do
|
121
|
+
buffer = StringIO.new
|
122
|
+
writer = Avro::DataFile::Writer.new buffer, (Avro::IO::DatumWriter.new schema), schema
|
123
|
+
writer << metadata_and_payload
|
124
|
+
writer.close
|
125
|
+
buffer.string
|
126
|
+
end
|
127
|
+
|
128
|
+
its(:schema) { should eq schema }
|
129
|
+
its(:payload) { should eq({ 'customer_id' => '123' }) }
|
130
|
+
its(:metadata) { should eq({
|
131
|
+
'application' => 'rabbit_feed',
|
132
|
+
'name' => 'test_event',
|
133
|
+
'host' => 'localhost',
|
134
|
+
'version' => '1.0.0',
|
135
|
+
'environment' => 'test',
|
136
|
+
'created_at_utc' => '2015-02-22',
|
137
|
+
})}
|
138
|
+
end
|
139
|
+
end
|
32
140
|
end
|
33
141
|
end
|