deimos-ruby 1.10.2 → 1.12.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Gemfile.lock +8 -8
  4. data/README.md +150 -16
  5. data/deimos-ruby.gemspec +1 -1
  6. data/docs/CONFIGURATION.md +4 -0
  7. data/lib/deimos/active_record_consume/batch_consumption.rb +1 -1
  8. data/lib/deimos/active_record_consume/message_consumption.rb +4 -3
  9. data/lib/deimos/active_record_consumer.rb +2 -2
  10. data/lib/deimos/active_record_producer.rb +3 -0
  11. data/lib/deimos/config/configuration.rb +29 -0
  12. data/lib/deimos/consume/batch_consumption.rb +2 -2
  13. data/lib/deimos/consume/message_consumption.rb +2 -2
  14. data/lib/deimos/consumer.rb +10 -0
  15. data/lib/deimos/producer.rb +4 -3
  16. data/lib/deimos/schema_backends/avro_base.rb +64 -1
  17. data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -1
  18. data/lib/deimos/schema_backends/base.rb +18 -2
  19. data/lib/deimos/schema_class/base.rb +67 -0
  20. data/lib/deimos/schema_class/enum.rb +24 -0
  21. data/lib/deimos/schema_class/record.rb +59 -0
  22. data/lib/deimos/shared_config.rb +5 -0
  23. data/lib/deimos/test_helpers.rb +70 -17
  24. data/lib/deimos/utils/schema_class.rb +29 -0
  25. data/lib/deimos/version.rb +1 -1
  26. data/lib/deimos.rb +3 -0
  27. data/lib/generators/deimos/schema_class/templates/schema_class.rb.tt +15 -0
  28. data/lib/generators/deimos/schema_class/templates/schema_enum.rb.tt +21 -0
  29. data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +65 -0
  30. data/lib/generators/deimos/schema_class_generator.rb +247 -0
  31. data/lib/tasks/deimos.rake +8 -0
  32. data/spec/active_record_batch_consumer_spec.rb +120 -110
  33. data/spec/active_record_consumer_spec.rb +97 -88
  34. data/spec/active_record_producer_spec.rb +38 -27
  35. data/spec/batch_consumer_spec.rb +37 -28
  36. data/spec/config/configuration_spec.rb +10 -3
  37. data/spec/consumer_spec.rb +94 -83
  38. data/spec/generators/active_record_generator_spec.rb +1 -0
  39. data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +206 -0
  40. data/spec/generators/schema_class_generator_spec.rb +186 -0
  41. data/spec/producer_spec.rb +110 -0
  42. data/spec/schema_classes/generated.rb +156 -0
  43. data/spec/schema_classes/my_nested_schema.rb +114 -0
  44. data/spec/schema_classes/my_schema.rb +53 -0
  45. data/spec/schema_classes/my_schema_key.rb +35 -0
  46. data/spec/schema_classes/my_schema_with_complex_types.rb +172 -0
  47. data/spec/schemas/com/my-namespace/Generated.avsc +6 -0
  48. data/spec/schemas/com/my-namespace/MySchemaWithComplexTypes.avsc +95 -0
  49. data/spec/spec_helper.rb +6 -1
  50. metadata +28 -4
@@ -5,7 +5,6 @@ require 'date'
5
5
  # Wrapped in a module to prevent class leakage
6
6
  module ActiveRecordConsumerTest
7
7
  describe Deimos::ActiveRecordConsumer, 'Message Consumer' do
8
-
9
8
  before(:all) do
10
9
  ActiveRecord::Base.connection.create_table(:widgets, force: true) do |t|
11
10
  t.string(:test_id)
@@ -61,94 +60,104 @@ module ActiveRecordConsumerTest
61
60
  Time.zone = 'Eastern Time (US & Canada)'
62
61
  end
63
62
 
64
- it 'should receive events correctly' do
65
- travel 1.day do
66
- expect(Widget.count).to eq(0)
67
- test_consume_message(MyConsumer, {
68
- test_id: 'abc',
69
- some_int: 3,
70
- updated_at: 1.day.ago.to_i,
71
- some_datetime_int: Time.zone.now.to_i,
72
- timestamp: 2.minutes.ago.to_s
73
- }, { call_original: true, key: 5 })
74
-
75
- expect(Widget.count).to eq(1)
76
- widget = Widget.last
77
- expect(widget.id).to eq(5)
78
- expect(widget.test_id).to eq('abc')
79
- expect(widget.some_int).to eq(3)
80
- expect(widget.some_datetime_int).to eq(Time.zone.now)
81
- expect(widget.some_bool).to eq(false)
82
- expect(widget.updated_at).to eq(Time.zone.now)
83
-
84
- # test unscoped
85
- widget.update_attribute(:some_bool, true)
86
-
87
- # test update
88
- test_consume_message(MyConsumer, {
89
- test_id: 'abcd',
90
- some_int: 3,
91
- some_datetime_int: Time.zone.now.to_i,
92
- timestamp: 2.minutes.ago.to_s
93
- }, { call_original: true, key: 5 })
94
- expect(Widget.unscoped.count).to eq(1)
95
- widget = Widget.unscoped.last
96
- expect(widget.id).to eq(5)
97
- expect(widget.test_id).to eq('abcd')
98
- expect(widget.some_int).to eq(3)
99
-
100
- # test delete
101
- test_consume_message(MyConsumer, nil, call_original: true, key: 5)
102
- expect(Widget.count).to eq(0)
103
-
63
+ describe 'consume' do
64
+ SCHEMA_CLASS_SETTINGS.each do |setting, use_schema_classes|
65
+ context "with Schema Class consumption #{setting}" do
66
+ before(:each) do
67
+ Deimos.configure { |config| config.schema.use_schema_classes = use_schema_classes }
68
+ end
69
+
70
+ it 'should receive events correctly' do
71
+ travel 1.day do
72
+ expect(Widget.count).to eq(0)
73
+ test_consume_message(MyConsumer, {
74
+ test_id: 'abc',
75
+ some_int: 3,
76
+ updated_at: 1.day.ago.to_i,
77
+ some_datetime_int: Time.zone.now.to_i,
78
+ timestamp: 2.minutes.ago.to_s
79
+ }, { call_original: true, key: 5 })
80
+
81
+ expect(Widget.count).to eq(1)
82
+ widget = Widget.last
83
+ expect(widget.id).to eq(5)
84
+ expect(widget.test_id).to eq('abc')
85
+ expect(widget.some_int).to eq(3)
86
+ expect(widget.some_datetime_int).to eq(Time.zone.now)
87
+ expect(widget.some_bool).to eq(false)
88
+ expect(widget.updated_at).to eq(Time.zone.now)
89
+
90
+ # test unscoped
91
+ widget.update_attribute(:some_bool, true)
92
+
93
+ # test update
94
+ test_consume_message(MyConsumer, {
95
+ test_id: 'abcd',
96
+ some_int: 3,
97
+ some_datetime_int: Time.zone.now.to_i,
98
+ timestamp: 2.minutes.ago.to_s
99
+ }, { call_original: true, key: 5 })
100
+ expect(Widget.unscoped.count).to eq(1)
101
+ widget = Widget.unscoped.last
102
+ expect(widget.id).to eq(5)
103
+ expect(widget.test_id).to eq('abcd')
104
+ expect(widget.some_int).to eq(3)
105
+
106
+ # test delete
107
+ test_consume_message(MyConsumer, nil, call_original: true, key: 5)
108
+ expect(Widget.count).to eq(0)
109
+
110
+ end
111
+
112
+ end
113
+
114
+ it 'should update only updated_at' do
115
+ travel_to Time.local(2020, 5, 5, 5, 5, 5)
116
+ widget1 = Widget.create!(test_id: 'id1', some_int: 3)
117
+ expect(widget1.updated_at.in_time_zone).to eq(Time.local(2020, 5, 5, 5, 5, 5))
118
+
119
+ travel 1.day
120
+ test_consume_message(MyCustomFetchConsumer, {
121
+ test_id: 'id1',
122
+ some_int: 3
123
+ }, { call_original: true })
124
+ expect(widget1.reload.updated_at.in_time_zone).
125
+ to eq(Time.local(2020, 5, 6, 5, 5, 5))
126
+ travel_back
127
+ end
128
+
129
+ it 'should find widgets by custom logic' do
130
+ widget1 = Widget.create!(test_id: 'id1')
131
+ expect(widget1.some_int).to be_nil
132
+ test_consume_message(MyCustomFetchConsumer, {
133
+ test_id: 'id1',
134
+ some_int: 3
135
+ }, { call_original: true })
136
+ expect(widget1.reload.some_int).to eq(3)
137
+ expect(Widget.count).to eq(1)
138
+ test_consume_message(MyCustomFetchConsumer, {
139
+ test_id: 'id2',
140
+ some_int: 4
141
+ }, { call_original: true })
142
+ expect(Widget.count).to eq(2)
143
+ expect(Widget.find_by_test_id('id1').some_int).to eq(3)
144
+ expect(Widget.find_by_test_id('id2').some_int).to eq(4)
145
+ end
146
+
147
+ it 'should not create record of process_message returns false' do
148
+ allow_any_instance_of(MyConsumer).to receive(:process_message?).and_return(false)
149
+ expect(Widget.count).to eq(0)
150
+ test_consume_message(MyConsumer, {
151
+ test_id: 'abc',
152
+ some_int: 3,
153
+ updated_at: 1.day.ago.to_i,
154
+ some_datetime_int: Time.zone.now.to_i,
155
+ timestamp: 2.minutes.ago.to_s
156
+ }, { call_original: true, key: 5 })
157
+ expect(Widget.count).to eq(0)
158
+ end
159
+ end
104
160
  end
105
-
106
- end
107
-
108
- it 'should update only updated_at' do
109
- travel_to Time.local(2020, 5, 5, 5, 5, 5)
110
- widget1 = Widget.create!(test_id: 'id1', some_int: 3)
111
- expect(widget1.updated_at.in_time_zone).to eq(Time.local(2020, 5, 5, 5, 5, 5))
112
-
113
- travel 1.day
114
- test_consume_message(MyCustomFetchConsumer, {
115
- test_id: 'id1',
116
- some_int: 3
117
- }, { call_original: true })
118
- expect(widget1.reload.updated_at.in_time_zone).
119
- to eq(Time.local(2020, 5, 6, 5, 5, 5))
120
- travel_back
121
- end
122
-
123
- it 'should find widgets by custom logic' do
124
- widget1 = Widget.create!(test_id: 'id1')
125
- expect(widget1.some_int).to be_nil
126
- test_consume_message(MyCustomFetchConsumer, {
127
- test_id: 'id1',
128
- some_int: 3
129
- }, { call_original: true })
130
- expect(widget1.reload.some_int).to eq(3)
131
- expect(Widget.count).to eq(1)
132
- test_consume_message(MyCustomFetchConsumer, {
133
- test_id: 'id2',
134
- some_int: 4
135
- }, { call_original: true })
136
- expect(Widget.count).to eq(2)
137
- expect(Widget.find_by_test_id('id1').some_int).to eq(3)
138
- expect(Widget.find_by_test_id('id2').some_int).to eq(4)
139
- end
140
-
141
- it 'should not create record of process_message returns false' do
142
- MyConsumer.any_instance.stub(:process_message?).and_return(false)
143
- expect(Widget.count).to eq(0)
144
- test_consume_message(MyConsumer, {
145
- test_id: 'abc',
146
- some_int: 3,
147
- updated_at: 1.day.ago.to_i,
148
- some_datetime_int: Time.zone.now.to_i,
149
- timestamp: 2.minutes.ago.to_s
150
- }, { call_original: true, key: 5 })
151
- expect(Widget.count).to eq(0)
152
161
  end
153
162
  end
154
163
  end
@@ -47,35 +47,46 @@ describe Deimos::ActiveRecordProducer do
47
47
  stub_const('MyProducerWithUniqueID', producer_class)
48
48
  end
49
49
 
50
- it 'should send events correctly' do
51
- MyProducer.send_event(Widget.new(test_id: 'abc', some_int: 3))
52
- expect('my-topic').to have_sent(test_id: 'abc', some_int: 3)
53
- end
50
+ describe 'produce' do
51
+ SCHEMA_CLASS_SETTINGS.each do |setting, use_schema_classes|
52
+ context "with Schema Class consumption #{setting}" do
53
+ before(:each) do
54
+ Deimos.configure { |config| config.schema.use_schema_classes = use_schema_classes }
55
+ end
54
56
 
55
- it 'should coerce values' do
56
- MyProducer.send_event(Widget.new(test_id: 'abc', some_int: '3'))
57
- MyProducer.send_event(Widget.new(test_id: 'abc', some_int: 4.5))
58
- expect('my-topic').to have_sent(test_id: 'abc', some_int: 3)
59
- expect('my-topic').to have_sent(test_id: 'abc', some_int: 4)
60
- expect {
61
- MyProducer.send_event(Widget.new(test_id: 'abc', some_int: nil))
62
- }.to raise_error(Avro::SchemaValidator::ValidationError)
63
-
64
- MyBooleanProducer.send_event(Widget.new(test_id: 'abc', some_bool: nil))
65
- MyBooleanProducer.send_event(Widget.new(test_id: 'abc', some_bool: true))
66
- expect('my-topic-with-boolean').to have_sent(test_id: 'abc', some_bool: false)
67
- expect('my-topic-with-boolean').to have_sent(test_id: 'abc', some_bool: true)
68
- end
57
+ it 'should send events correctly' do
58
+ MyProducer.send_event(Widget.new(test_id: 'abc', some_int: 3))
59
+ expect('my-topic').to have_sent(test_id: 'abc', some_int: 3)
60
+ end
61
+
62
+ it 'should coerce values' do
63
+ MyProducer.send_event(Widget.new(test_id: 'abc', some_int: '3'))
64
+ MyProducer.send_event(Widget.new(test_id: 'abc', some_int: 4.5))
65
+ expect('my-topic').to have_sent(test_id: 'abc', some_int: 3)
66
+ expect('my-topic').to have_sent(test_id: 'abc', some_int: 4)
67
+ expect {
68
+ MyProducer.send_event(Widget.new(test_id: 'abc', some_int: nil))
69
+ }.to raise_error(Avro::SchemaValidator::ValidationError)
70
+
71
+ MyBooleanProducer.send_event(Widget.new(test_id: 'abc', some_bool: nil))
72
+ MyBooleanProducer.send_event(Widget.new(test_id: 'abc', some_bool: true))
73
+ expect('my-topic-with-boolean').to have_sent(test_id: 'abc', some_bool: false)
74
+ expect('my-topic-with-boolean').to have_sent(test_id: 'abc', some_bool: true)
75
+ end
69
76
 
70
- it 'should be able to call the record' do
71
- widget = Widget.create!(test_id: 'abc2', some_int: 3)
72
- MyProducerWithID.send_event(id: widget.id, test_id: 'abc2', some_int: 3)
73
- expect('my-topic-with-id').to have_sent(
74
- test_id: 'abc2',
75
- some_int: 3,
76
- message_id: 'generated_id',
77
- timestamp: anything
78
- )
77
+ it 'should be able to call the record' do
78
+ widget = Widget.create!(test_id: 'abc2', some_int: 3)
79
+ MyProducerWithID.send_event(id: widget.id, test_id: 'abc2', some_int: 3)
80
+ expect('my-topic-with-id').to have_sent(
81
+ test_id: 'abc2',
82
+ some_int: 3,
83
+ message_id: 'generated_id',
84
+ timestamp: anything
85
+ )
86
+ end
87
+
88
+ end
89
+ end
79
90
  end
80
91
 
81
92
  specify '#watched_attributes' do
@@ -3,7 +3,6 @@
3
3
  # :nodoc:
4
4
  module ConsumerTest
5
5
  describe Deimos::Consumer, 'Batch Consumer' do
6
-
7
6
  prepend_before(:each) do
8
7
  # :nodoc:
9
8
  consumer_class = Class.new(described_class) do
@@ -30,40 +29,50 @@ module ConsumerTest
30
29
  batch.concat([{ 'invalid' => 'key' }])
31
30
  end
32
31
 
33
- it 'should provide backwards compatibility for BatchConsumer class' do
34
- consumer_class = Class.new(Deimos::BatchConsumer) do
35
- schema 'MySchema'
36
- namespace 'com.my-namespace'
37
- key_config field: 'test_id'
32
+ describe 'consume_batch' do
33
+ SCHEMA_CLASS_SETTINGS.each do |setting, use_schema_classes|
34
+ context "with Schema Class consumption #{setting}" do
35
+ before(:each) do
36
+ Deimos.configure { |config| config.schema.use_schema_classes = use_schema_classes }
37
+ end
38
38
 
39
- # :nodoc:
40
- def consume_batch(_payloads, _metadata)
41
- raise 'This should not be called unless call_original is set'
42
- end
43
- end
44
- stub_const('ConsumerTest::MyOldBatchConsumer', consumer_class)
39
+ it 'should provide backwards compatibility for BatchConsumer class' do
40
+ consumer_class = Class.new(Deimos::BatchConsumer) do
41
+ schema 'MySchema'
42
+ namespace 'com.my-namespace'
43
+ key_config field: 'test_id'
44
+
45
+ # :nodoc:
46
+ def consume_batch(_payloads, _metadata)
47
+ raise 'This should not be called unless call_original is set'
48
+ end
49
+ end
50
+ stub_const('ConsumerTest::MyOldBatchConsumer', consumer_class)
51
+
52
+ test_consume_batch(MyOldBatchConsumer, batch) do |received, _metadata|
53
+ expect(received).to eq(batch)
54
+ end
55
+ end
45
56
 
46
- test_consume_batch(MyOldBatchConsumer, batch) do |received, _metadata|
47
- expect(received).to eq(batch)
48
- end
49
- end
57
+ it 'should consume a batch of messages' do
58
+ test_consume_batch(MyBatchConsumer, batch) do |received, _metadata|
59
+ expect(received).to eq(batch)
60
+ end
61
+ end
50
62
 
51
- it 'should consume a batch of messages' do
52
- test_consume_batch(MyBatchConsumer, batch) do |received, _metadata|
53
- expect(received).to eq(batch)
54
- end
55
- end
63
+ it 'should consume a message on a topic' do
64
+ test_consume_batch('my_batch_consume_topic', batch) do |received, _metadata|
65
+ expect(received).to eq(batch)
66
+ end
67
+ end
56
68
 
57
- it 'should consume a message on a topic' do
58
- test_consume_batch('my_batch_consume_topic', batch) do |received, _metadata|
59
- expect(received).to eq(batch)
69
+ it 'should fail on an invalid message in the batch' do
70
+ test_consume_batch_invalid_message(MyBatchConsumer, batch.concat(invalid_payloads))
71
+ end
72
+ end
60
73
  end
61
74
  end
62
75
 
63
- it 'should fail on an invalid message in the batch' do
64
- test_consume_batch_invalid_message(MyBatchConsumer, batch.concat(invalid_payloads))
65
- end
66
-
67
76
  describe 'when reraising errors is disabled' do
68
77
  before(:each) do
69
78
  Deimos.configure { |config| config.consumers.reraise_errors = false }
@@ -21,6 +21,7 @@ describe Deimos, 'configuration' do
21
21
  kafka_logger logger
22
22
  reraise_consumer_errors true
23
23
  schema_registry_url 'http://schema.registry'
24
+ schema.use_schema_classes false
24
25
  seed_broker 'whatever'
25
26
  schema_path 'some_path'
26
27
  producer_schema_namespace 'namespace'
@@ -37,6 +38,7 @@ describe Deimos, 'configuration' do
37
38
  expect(described_class.config.kafka.logger).to eq(logger)
38
39
  expect(described_class.config.consumers.reraise_errors).to eq(true)
39
40
  expect(described_class.config.schema.registry_url).to eq('http://schema.registry')
41
+ expect(described_class.config.schema.use_schema_classes).to eq(false)
40
42
  expect(described_class.config.kafka.seed_brokers).to eq('whatever')
41
43
  expect(described_class.config.producers.schema_namespace).to eq('namespace')
42
44
  expect(described_class.config.producers.topic_prefix).to eq('prefix')
@@ -86,7 +88,8 @@ describe Deimos, 'configuration' do
86
88
  offset_commit_threshold: 0,
87
89
  offset_retention_time: nil,
88
90
  heartbeat_interval: 10,
89
- handler: 'ConsumerTest::MyConsumer'
91
+ handler: 'ConsumerTest::MyConsumer',
92
+ use_schema_classes: nil
90
93
  }, {
91
94
  topic: 'my_batch_consume_topic',
92
95
  group_id: 'my_batch_group_id',
@@ -102,7 +105,8 @@ describe Deimos, 'configuration' do
102
105
  offset_commit_threshold: 0,
103
106
  offset_retention_time: nil,
104
107
  heartbeat_interval: 10,
105
- handler: 'ConsumerTest::MyBatchConsumer'
108
+ handler: 'ConsumerTest::MyBatchConsumer',
109
+ use_schema_classes: nil
106
110
  }
107
111
  ],
108
112
  producer: {
@@ -178,6 +182,7 @@ describe Deimos, 'configuration' do
178
182
  offset_commit_threshold 13
179
183
  offset_retention_time 13
180
184
  heartbeat_interval 13
185
+ use_schema_classes false
181
186
  end
182
187
  consumer do
183
188
  disabled true
@@ -185,6 +190,7 @@ describe Deimos, 'configuration' do
185
190
  schema 'blah2'
186
191
  topic 'blah2'
187
192
  group_id 'myconsumerid2'
193
+ use_schema_classes false
188
194
  end
189
195
  end
190
196
 
@@ -227,7 +233,8 @@ describe Deimos, 'configuration' do
227
233
  offset_commit_threshold: 13,
228
234
  offset_retention_time: 13,
229
235
  heartbeat_interval: 13,
230
- handler: 'MyConfigConsumer'
236
+ handler: 'MyConfigConsumer',
237
+ use_schema_classes: false
231
238
  }
232
239
  ],
233
240
  producer: {
@@ -3,7 +3,6 @@
3
3
  # :nodoc:
4
4
  module ConsumerTest
5
5
  describe Deimos::Consumer, 'Message Consumer' do
6
-
7
6
  prepend_before(:each) do
8
7
  # :nodoc:
9
8
  consumer_class = Class.new(described_class) do
@@ -24,98 +23,110 @@ module ConsumerTest
24
23
  stub_const('ConsumerTest::MyConsumer', consumer_class)
25
24
  end
26
25
 
27
- it 'should consume a message' do
28
- test_consume_message(MyConsumer,
29
- 'test_id' => 'foo',
30
- 'some_int' => 123) do |payload, _metadata|
31
- expect(payload['test_id']).to eq('foo')
32
- end
33
- end
26
+ describe 'consume' do
27
+ SCHEMA_CLASS_SETTINGS.each do |setting, use_schema_classes|
28
+ context "with Schema Class consumption #{setting}" do
29
+ before(:each) do
30
+ Deimos.configure { |config| config.schema.use_schema_classes = use_schema_classes }
31
+ end
34
32
 
35
- it 'should consume a nil message' do
36
- test_consume_message(MyConsumer, nil) do |payload, _metadata|
37
- expect(payload).to be_nil
38
- end
39
- end
33
+ it 'should consume a message' do
34
+ test_consume_message(MyConsumer,
35
+ 'test_id' => 'foo',
36
+ 'some_int' => 123) do |payload, _metadata|
37
+ expect(payload['test_id']).to eq('foo')
38
+ expect(payload['some_int']).to eq(123)
39
+ end
40
+ end
40
41
 
41
- it 'should consume a message idempotently' do
42
- # testing for a crash and re-consuming the same message/metadata
43
- key = { 'test_id' => 'foo' }
44
- test_metadata = { key: key }
45
- allow_any_instance_of(MyConsumer).to(receive(:decode_key)) do |_instance, k|
46
- k['test_id']
47
- end
48
- MyConsumer.new.around_consume({ 'test_id' => 'foo',
49
- 'some_int' => 123 }, test_metadata) do |_payload, metadata|
50
- expect(metadata[:key]).to eq('foo')
51
- end
52
- MyConsumer.new.around_consume({ 'test_id' => 'foo',
53
- 'some_int' => 123 }, test_metadata) do |_payload, metadata|
54
- expect(metadata[:key]).to eq('foo')
55
- end
56
- end
42
+ it 'should consume a nil message' do
43
+ test_consume_message(MyConsumer, nil) do |payload, _metadata|
44
+ expect(payload).to be_nil
45
+ end
46
+ end
57
47
 
58
- it 'should consume a message on a topic' do
59
- test_consume_message('my_consume_topic',
60
- 'test_id' => 'foo',
61
- 'some_int' => 123) do |payload, _metadata|
62
- expect(payload['test_id']).to eq('foo')
63
- end
64
- end
48
+ it 'should consume a message idempotently' do
49
+ # testing for a crash and re-consuming the same message/metadata
50
+ key = { 'test_id' => 'foo' }
51
+ test_metadata = { key: key }
52
+ allow_any_instance_of(MyConsumer).to(receive(:decode_key)) do |_instance, k|
53
+ k['test_id']
54
+ end
55
+ MyConsumer.new.around_consume({ 'test_id' => 'foo',
56
+ 'some_int' => 123 }, test_metadata) do |_payload, metadata|
57
+ expect(metadata[:key]).to eq('foo')
58
+ end
59
+ MyConsumer.new.around_consume({ 'test_id' => 'foo',
60
+ 'some_int' => 123 }, test_metadata) do |_payload, metadata|
61
+ expect(metadata[:key]).to eq('foo')
62
+ end
63
+ end
65
64
 
66
- it 'should fail on invalid message' do
67
- test_consume_invalid_message(MyConsumer, 'invalid' => 'key')
68
- end
65
+ it 'should consume a message on a topic' do
66
+ test_consume_message('my_consume_topic',
67
+ 'test_id' => 'foo',
68
+ 'some_int' => 123) do |payload, _metadata|
69
+ expect(payload['test_id']).to eq('foo')
70
+ expect(payload['some_int']).to eq(123)
71
+ end
72
+ end
69
73
 
70
- it 'should fail if reraise is false but fatal_error is true' do
71
- Deimos.configure { |config| config.consumers.reraise_errors = false }
72
- test_consume_invalid_message(MyConsumer, 'fatal')
73
- end
74
+ it 'should fail on invalid message' do
75
+ test_consume_invalid_message(MyConsumer, 'invalid' => 'key')
76
+ end
74
77
 
75
- it 'should fail if fatal_error is true globally' do
76
- Deimos.configure do |config|
77
- config.consumers.fatal_error = proc { true }
78
- config.consumers.reraise_errors = false
79
- end
80
- test_consume_invalid_message(MyConsumer, 'invalid' => 'key')
81
- end
78
+ it 'should fail if reraise is false but fatal_error is true' do
79
+ Deimos.configure { |config| config.consumers.reraise_errors = false }
80
+ test_consume_invalid_message(MyConsumer, 'fatal')
81
+ end
82
82
 
83
- it 'should fail on message with extra fields' do
84
- test_consume_invalid_message(MyConsumer,
85
- 'test_id' => 'foo',
86
- 'some_int' => 123,
87
- 'extra_field' => 'field name')
88
- end
83
+ it 'should fail if fatal_error is true globally' do
84
+ Deimos.configure do |config|
85
+ config.consumers.fatal_error = proc { true }
86
+ config.consumers.reraise_errors = false
87
+ end
88
+ test_consume_invalid_message(MyConsumer, 'invalid' => 'key')
89
+ end
89
90
 
90
- it 'should not fail when before_consume fails without reraising errors' do
91
- Deimos.configure { |config| config.consumers.reraise_errors = false }
92
- expect {
93
- test_consume_message(
94
- MyConsumer,
95
- { 'test_id' => 'foo',
96
- 'some_int' => 123 },
97
- { skip_expectation: true }
98
- ) { raise 'OH NOES' }
99
- }.not_to raise_error
100
- end
91
+ it 'should fail on message with extra fields' do
92
+ test_consume_invalid_message(MyConsumer,
93
+ 'test_id' => 'foo',
94
+ 'some_int' => 123,
95
+ 'extra_field' => 'field name')
96
+ end
101
97
 
102
- it 'should not fail when consume fails without reraising errors' do
103
- Deimos.configure { |config| config.consumers.reraise_errors = false }
104
- expect {
105
- test_consume_message(
106
- MyConsumer,
107
- { 'invalid' => 'key' },
108
- { skip_expectation: true }
109
- )
110
- }.not_to raise_error
111
- end
98
+ it 'should not fail when before_consume fails without reraising errors' do
99
+ Deimos.configure { |config| config.consumers.reraise_errors = false }
100
+ expect {
101
+ test_consume_message(
102
+ MyConsumer,
103
+ { 'test_id' => 'foo',
104
+ 'some_int' => 123 },
105
+ { skip_expectation: true }
106
+ ) { raise 'OH NOES' }
107
+ }.not_to raise_error
108
+ end
109
+
110
+ it 'should not fail when consume fails without reraising errors' do
111
+ Deimos.configure { |config| config.consumers.reraise_errors = false }
112
+ expect {
113
+ test_consume_message(
114
+ MyConsumer,
115
+ { 'invalid' => 'key' },
116
+ { skip_expectation: true }
117
+ )
118
+ }.not_to raise_error
119
+ end
112
120
 
113
- it 'should call original' do
114
- expect {
115
- test_consume_message(MyConsumer,
116
- { 'test_id' => 'foo', 'some_int' => 123 },
117
- { call_original: true })
118
- }.to raise_error('This should not be called unless call_original is set')
121
+ it 'should call original' do
122
+ expect {
123
+ test_consume_message(MyConsumer,
124
+ { 'test_id' => 'foo', 'some_int' => 123 },
125
+ { call_original: true })
126
+ }.to raise_error('This should not be called unless call_original is set')
127
+ end
128
+ end
129
+ end
119
130
  end
120
131
 
121
132
  describe 'decode_key' do
@@ -28,6 +28,7 @@ RSpec.describe Deimos::Generators::ActiveRecordGenerator do
28
28
  t.bigint :a_long
29
29
  t.float :a_float
30
30
  t.float :a_double
31
+ t.string :an_optional_int
31
32
  t.string :an_enum
32
33
  t.json :an_array
33
34
  t.json :a_map