deimos-temp-fork 0.0.1

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 (146) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +83 -0
  3. data/.gitignore +41 -0
  4. data/.gitmodules +0 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +333 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/CHANGELOG.md +349 -0
  10. data/CODE_OF_CONDUCT.md +77 -0
  11. data/Dockerfile +23 -0
  12. data/Gemfile +6 -0
  13. data/Gemfile.lock +286 -0
  14. data/Guardfile +22 -0
  15. data/LICENSE.md +195 -0
  16. data/README.md +1099 -0
  17. data/Rakefile +13 -0
  18. data/bin/deimos +4 -0
  19. data/deimos-ruby.gemspec +44 -0
  20. data/docker-compose.yml +71 -0
  21. data/docs/ARCHITECTURE.md +140 -0
  22. data/docs/CONFIGURATION.md +236 -0
  23. data/docs/DATABASE_BACKEND.md +147 -0
  24. data/docs/INTEGRATION_TESTS.md +52 -0
  25. data/docs/PULL_REQUEST_TEMPLATE.md +35 -0
  26. data/docs/UPGRADING.md +128 -0
  27. data/lib/deimos-temp-fork.rb +95 -0
  28. data/lib/deimos/active_record_consume/batch_consumption.rb +164 -0
  29. data/lib/deimos/active_record_consume/batch_slicer.rb +27 -0
  30. data/lib/deimos/active_record_consume/message_consumption.rb +79 -0
  31. data/lib/deimos/active_record_consume/schema_model_converter.rb +52 -0
  32. data/lib/deimos/active_record_consumer.rb +67 -0
  33. data/lib/deimos/active_record_producer.rb +87 -0
  34. data/lib/deimos/backends/base.rb +32 -0
  35. data/lib/deimos/backends/db.rb +41 -0
  36. data/lib/deimos/backends/kafka.rb +33 -0
  37. data/lib/deimos/backends/kafka_async.rb +33 -0
  38. data/lib/deimos/backends/test.rb +20 -0
  39. data/lib/deimos/batch_consumer.rb +7 -0
  40. data/lib/deimos/config/configuration.rb +381 -0
  41. data/lib/deimos/config/phobos_config.rb +137 -0
  42. data/lib/deimos/consume/batch_consumption.rb +150 -0
  43. data/lib/deimos/consume/message_consumption.rb +94 -0
  44. data/lib/deimos/consumer.rb +104 -0
  45. data/lib/deimos/instrumentation.rb +76 -0
  46. data/lib/deimos/kafka_message.rb +60 -0
  47. data/lib/deimos/kafka_source.rb +128 -0
  48. data/lib/deimos/kafka_topic_info.rb +102 -0
  49. data/lib/deimos/message.rb +79 -0
  50. data/lib/deimos/metrics/datadog.rb +47 -0
  51. data/lib/deimos/metrics/mock.rb +39 -0
  52. data/lib/deimos/metrics/provider.rb +36 -0
  53. data/lib/deimos/monkey_patches/phobos_cli.rb +35 -0
  54. data/lib/deimos/monkey_patches/phobos_producer.rb +51 -0
  55. data/lib/deimos/poll_info.rb +9 -0
  56. data/lib/deimos/producer.rb +224 -0
  57. data/lib/deimos/railtie.rb +8 -0
  58. data/lib/deimos/schema_backends/avro_base.rb +140 -0
  59. data/lib/deimos/schema_backends/avro_local.rb +30 -0
  60. data/lib/deimos/schema_backends/avro_schema_coercer.rb +119 -0
  61. data/lib/deimos/schema_backends/avro_schema_registry.rb +34 -0
  62. data/lib/deimos/schema_backends/avro_validation.rb +21 -0
  63. data/lib/deimos/schema_backends/base.rb +150 -0
  64. data/lib/deimos/schema_backends/mock.rb +42 -0
  65. data/lib/deimos/shared_config.rb +63 -0
  66. data/lib/deimos/test_helpers.rb +360 -0
  67. data/lib/deimos/tracing/datadog.rb +35 -0
  68. data/lib/deimos/tracing/mock.rb +40 -0
  69. data/lib/deimos/tracing/provider.rb +29 -0
  70. data/lib/deimos/utils/db_poller.rb +150 -0
  71. data/lib/deimos/utils/db_producer.rb +243 -0
  72. data/lib/deimos/utils/deadlock_retry.rb +68 -0
  73. data/lib/deimos/utils/inline_consumer.rb +150 -0
  74. data/lib/deimos/utils/lag_reporter.rb +175 -0
  75. data/lib/deimos/utils/schema_controller_mixin.rb +115 -0
  76. data/lib/deimos/version.rb +5 -0
  77. data/lib/generators/deimos/active_record/templates/migration.rb.tt +28 -0
  78. data/lib/generators/deimos/active_record/templates/model.rb.tt +5 -0
  79. data/lib/generators/deimos/active_record_generator.rb +79 -0
  80. data/lib/generators/deimos/db_backend/templates/migration +25 -0
  81. data/lib/generators/deimos/db_backend/templates/rails3_migration +31 -0
  82. data/lib/generators/deimos/db_backend_generator.rb +48 -0
  83. data/lib/generators/deimos/db_poller/templates/migration +11 -0
  84. data/lib/generators/deimos/db_poller/templates/rails3_migration +16 -0
  85. data/lib/generators/deimos/db_poller_generator.rb +48 -0
  86. data/lib/tasks/deimos.rake +34 -0
  87. data/spec/active_record_batch_consumer_spec.rb +481 -0
  88. data/spec/active_record_consume/batch_slicer_spec.rb +42 -0
  89. data/spec/active_record_consume/schema_model_converter_spec.rb +105 -0
  90. data/spec/active_record_consumer_spec.rb +154 -0
  91. data/spec/active_record_producer_spec.rb +85 -0
  92. data/spec/backends/base_spec.rb +10 -0
  93. data/spec/backends/db_spec.rb +54 -0
  94. data/spec/backends/kafka_async_spec.rb +11 -0
  95. data/spec/backends/kafka_spec.rb +11 -0
  96. data/spec/batch_consumer_spec.rb +256 -0
  97. data/spec/config/configuration_spec.rb +248 -0
  98. data/spec/consumer_spec.rb +209 -0
  99. data/spec/deimos_spec.rb +169 -0
  100. data/spec/generators/active_record_generator_spec.rb +56 -0
  101. data/spec/handlers/my_batch_consumer.rb +10 -0
  102. data/spec/handlers/my_consumer.rb +10 -0
  103. data/spec/kafka_listener_spec.rb +55 -0
  104. data/spec/kafka_source_spec.rb +381 -0
  105. data/spec/kafka_topic_info_spec.rb +111 -0
  106. data/spec/message_spec.rb +19 -0
  107. data/spec/phobos.bad_db.yml +73 -0
  108. data/spec/phobos.yml +77 -0
  109. data/spec/producer_spec.rb +498 -0
  110. data/spec/rake_spec.rb +19 -0
  111. data/spec/schema_backends/avro_base_shared.rb +199 -0
  112. data/spec/schema_backends/avro_local_spec.rb +32 -0
  113. data/spec/schema_backends/avro_schema_registry_spec.rb +32 -0
  114. data/spec/schema_backends/avro_validation_spec.rb +24 -0
  115. data/spec/schema_backends/base_spec.rb +33 -0
  116. data/spec/schemas/com/my-namespace/Generated.avsc +71 -0
  117. data/spec/schemas/com/my-namespace/MyNestedSchema.avsc +62 -0
  118. data/spec/schemas/com/my-namespace/MySchema-key.avsc +13 -0
  119. data/spec/schemas/com/my-namespace/MySchema.avsc +18 -0
  120. data/spec/schemas/com/my-namespace/MySchemaCompound-key.avsc +18 -0
  121. data/spec/schemas/com/my-namespace/MySchemaWithBooleans.avsc +18 -0
  122. data/spec/schemas/com/my-namespace/MySchemaWithDateTimes.avsc +33 -0
  123. data/spec/schemas/com/my-namespace/MySchemaWithId.avsc +28 -0
  124. data/spec/schemas/com/my-namespace/MySchemaWithUniqueId.avsc +32 -0
  125. data/spec/schemas/com/my-namespace/Wibble.avsc +43 -0
  126. data/spec/schemas/com/my-namespace/Widget.avsc +27 -0
  127. data/spec/schemas/com/my-namespace/WidgetTheSecond.avsc +27 -0
  128. data/spec/schemas/com/my-namespace/request/CreateTopic.avsc +11 -0
  129. data/spec/schemas/com/my-namespace/request/Index.avsc +11 -0
  130. data/spec/schemas/com/my-namespace/request/UpdateRequest.avsc +11 -0
  131. data/spec/schemas/com/my-namespace/response/CreateTopic.avsc +11 -0
  132. data/spec/schemas/com/my-namespace/response/Index.avsc +11 -0
  133. data/spec/schemas/com/my-namespace/response/UpdateResponse.avsc +11 -0
  134. data/spec/spec_helper.rb +267 -0
  135. data/spec/utils/db_poller_spec.rb +320 -0
  136. data/spec/utils/db_producer_spec.rb +514 -0
  137. data/spec/utils/deadlock_retry_spec.rb +74 -0
  138. data/spec/utils/inline_consumer_spec.rb +31 -0
  139. data/spec/utils/lag_reporter_spec.rb +76 -0
  140. data/spec/utils/platform_schema_validation_spec.rb +0 -0
  141. data/spec/utils/schema_controller_mixin_spec.rb +84 -0
  142. data/support/deimos-solo.png +0 -0
  143. data/support/deimos-with-name-next.png +0 -0
  144. data/support/deimos-with-name.png +0 -0
  145. data/support/flipp-logo.png +0 -0
  146. metadata +551 -0
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Deimos::KafkaListener do
4
+ include_context 'with widgets'
5
+
6
+ prepend_before(:each) do
7
+ producer_class = Class.new(Deimos::Producer) do
8
+ schema 'MySchema'
9
+ namespace 'com.my-namespace'
10
+ topic 'my-topic'
11
+ key_config none: true
12
+ end
13
+ stub_const('MyProducer', producer_class)
14
+ end
15
+
16
+ before(:each) do
17
+ Deimos.configure do |c|
18
+ c.producers.backend = :kafka
19
+ c.schema.backend = :avro_local
20
+ end
21
+ allow_any_instance_of(Kafka::Cluster).to receive(:add_target_topics)
22
+ allow_any_instance_of(Kafka::Cluster).to receive(:partitions_for).
23
+ and_raise(Kafka::Error)
24
+ end
25
+
26
+ describe '.send_produce_error' do
27
+ let(:payloads) do
28
+ [{ 'test_id' => 'foo', 'some_int' => 123 },
29
+ { 'test_id' => 'bar', 'some_int' => 124 }]
30
+ end
31
+
32
+ it 'should listen to publishing errors and republish as Deimos events' do
33
+ allow(Deimos::Producer).to receive(:descendants).and_return([MyProducer])
34
+ Deimos.subscribe('produce_error') do |event|
35
+ expect(event.payload).to include(
36
+ producer: MyProducer,
37
+ topic: 'my-topic',
38
+ payloads: payloads
39
+ )
40
+ end
41
+ expect(Deimos.config.metrics).to receive(:increment).
42
+ with('publish_error', tags: %w(topic:my-topic), by: 2)
43
+ expect { MyProducer.publish_list(payloads) }.to raise_error(Kafka::DeliveryFailed)
44
+ end
45
+
46
+ it 'should not send any notifications when producer is not found' do
47
+ Deimos.subscribe('produce_error') do |_|
48
+ raise 'OH NOES'
49
+ end
50
+ allow(Deimos::Producer).to receive(:descendants).and_return([])
51
+ expect(Deimos.config.metrics).not_to receive(:increment).with('publish_error', anything)
52
+ expect { MyProducer.publish_list(payloads) }.to raise_error(Kafka::DeliveryFailed)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,381 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'activerecord-import'
4
+
5
+ # Wrap in a module so our classes don't leak out afterwards
6
+ module KafkaSourceSpec
7
+ RSpec.describe Deimos::KafkaSource do
8
+ before(:all) do
9
+ ActiveRecord::Base.connection.create_table(:widgets, force: true) do |t|
10
+ t.integer(:widget_id)
11
+ t.string(:description)
12
+ t.string(:model_id, default: '')
13
+ t.string(:name)
14
+ t.timestamps
15
+ end
16
+ ActiveRecord::Base.connection.add_index(:widgets, :widget_id)
17
+
18
+ # Dummy producer which mimicks the behavior of a real producer
19
+ class WidgetProducer < Deimos::ActiveRecordProducer
20
+ topic 'my-topic'
21
+ namespace 'com.my-namespace'
22
+ schema 'Widget'
23
+ key_config field: :id
24
+ end
25
+
26
+ # Dummy producer which mimicks the behavior of a real producer
27
+ class WidgetProducerTheSecond < Deimos::ActiveRecordProducer
28
+ topic 'my-topic-the-second'
29
+ namespace 'com.my-namespace'
30
+ schema 'WidgetTheSecond'
31
+ key_config field: :id
32
+ end
33
+
34
+ # Dummy class we can include the mixin in. Has a backing table created
35
+ # earlier.
36
+ class Widget < ActiveRecord::Base
37
+ include Deimos::KafkaSource
38
+
39
+ # :nodoc:
40
+ def self.kafka_producers
41
+ [WidgetProducer, WidgetProducerTheSecond]
42
+ end
43
+ end
44
+ Widget.reset_column_information
45
+
46
+ end
47
+
48
+ after(:all) do
49
+ ActiveRecord::Base.connection.drop_table(:widgets)
50
+ end
51
+
52
+ before(:each) do
53
+ Widget.delete_all
54
+ end
55
+
56
+ it 'should send events on creation, update, and deletion' do
57
+ widget = Widget.create!(widget_id: 1, name: 'widget')
58
+ expect('my-topic').to have_sent({
59
+ widget_id: 1,
60
+ name: 'widget',
61
+ id: widget.id,
62
+ created_at: anything,
63
+ updated_at: anything
64
+ }, 1)
65
+ expect('my-topic-the-second').to have_sent({
66
+ widget_id: 1,
67
+ model_id: '',
68
+ id: widget.id,
69
+ created_at: anything,
70
+ updated_at: anything
71
+ }, 1)
72
+ widget.update_attribute(:name, 'widget 2')
73
+ expect('my-topic').to have_sent({
74
+ widget_id: 1,
75
+ name: 'widget 2',
76
+ id: widget.id,
77
+ created_at: anything,
78
+ updated_at: anything
79
+ }, 1)
80
+ expect('my-topic-the-second').to have_sent({
81
+ widget_id: 1,
82
+ model_id: '',
83
+ id: widget.id,
84
+ created_at: anything,
85
+ updated_at: anything
86
+ }, 1)
87
+ widget.destroy
88
+ expect('my-topic').to have_sent(nil, 1)
89
+ expect('my-topic-the-second').to have_sent(nil, 1)
90
+ end
91
+
92
+ it 'should not call generate_payload but still publish a nil payload for deletion' do
93
+ widget = Widget.create!(widget_id: '808', name: 'delete_me!')
94
+ expect(Deimos::ActiveRecordProducer).not_to receive(:generate_payload)
95
+ widget.destroy
96
+ expect('my-topic').to have_sent(nil, widget.id)
97
+ expect('my-topic-the-second').to have_sent(nil, widget.id)
98
+ end
99
+
100
+ it 'should send events on import' do
101
+ widgets = (1..3).map do |i|
102
+ Widget.new(widget_id: i, name: "Widget #{i}")
103
+ end
104
+ Widget.import(widgets)
105
+ widgets = Widget.all
106
+ expect('my-topic').to have_sent({
107
+ widget_id: 1,
108
+ name: 'Widget 1',
109
+ id: widgets[0].id,
110
+ created_at: anything,
111
+ updated_at: anything
112
+ }, widgets[0].id)
113
+ expect('my-topic').to have_sent({
114
+ widget_id: 2,
115
+ name: 'Widget 2',
116
+ id: widgets[1].id,
117
+ created_at: anything,
118
+ updated_at: anything
119
+ }, widgets[1].id)
120
+ expect('my-topic').to have_sent({
121
+ widget_id: 3,
122
+ name: 'Widget 3',
123
+ id: widgets[2].id,
124
+ created_at: anything,
125
+ updated_at: anything
126
+ }, widgets[2].id)
127
+ end
128
+
129
+ it 'should send events on import with on_duplicate_key_update and existing records' do
130
+ widget1 = Widget.create(widget_id: 1, name: 'Widget 1')
131
+ widget2 = Widget.create(widget_id: 2, name: 'Widget 2')
132
+ widget1.name = 'New Widget 1'
133
+ widget2.name = 'New Widget 2'
134
+ Widget.import([widget1, widget2], on_duplicate_key_update: %i(widget_id name))
135
+
136
+ expect('my-topic').to have_sent({
137
+ widget_id: 1,
138
+ name: 'New Widget 1',
139
+ id: widget1.id,
140
+ created_at: anything,
141
+ updated_at: anything
142
+ }, widget1.id)
143
+ expect('my-topic').to have_sent({
144
+ widget_id: 2,
145
+ name: 'New Widget 2',
146
+ id: widget2.id,
147
+ created_at: anything,
148
+ updated_at: anything
149
+ }, widget2.id)
150
+ end
151
+
152
+ it 'should not fail when mixing existing and new records for import :on_duplicate_key_update' do
153
+ widget1 = Widget.create(widget_id: 1, name: 'Widget 1')
154
+ expect('my-topic').to have_sent({
155
+ widget_id: 1,
156
+ name: 'Widget 1',
157
+ id: widget1.id,
158
+ created_at: anything,
159
+ updated_at: anything
160
+ }, widget1.id)
161
+
162
+ widget2 = Widget.new(widget_id: 2, name: 'Widget 2')
163
+ widget1.name = 'New Widget 1'
164
+ Widget.import([widget1, widget2], on_duplicate_key_update: %i(widget_id))
165
+ widgets = Widget.all
166
+ expect('my-topic').to have_sent({
167
+ widget_id: 1,
168
+ name: 'New Widget 1',
169
+ id: widgets[0].id,
170
+ created_at: anything,
171
+ updated_at: anything
172
+ }, widgets[0].id)
173
+ expect('my-topic').to have_sent({
174
+ widget_id: 2,
175
+ name: 'Widget 2',
176
+ id: widgets[1].id,
177
+ created_at: anything,
178
+ updated_at: anything
179
+ }, widgets[1].id)
180
+ end
181
+
182
+ it 'should send events even if the save fails' do
183
+ widget = Widget.create!(widget_id: 1, name: 'widget')
184
+ expect('my-topic').to have_sent({
185
+ widget_id: 1,
186
+ name: widget.name,
187
+ id: widget.id,
188
+ created_at: anything,
189
+ updated_at: anything
190
+ }, widget.id)
191
+ clear_kafka_messages!
192
+ Widget.transaction do
193
+ widget.update_attribute(:name, 'widget 3')
194
+ raise ActiveRecord::Rollback
195
+ end
196
+ expect('my-topic').to have_sent(anything)
197
+ end
198
+
199
+ it 'should not send events if an unrelated field changes' do
200
+ widget = Widget.create!(widget_id: 1, name: 'widget')
201
+ clear_kafka_messages!
202
+ widget.update_attribute(:description, 'some description')
203
+ expect('my-topic').not_to have_sent(anything)
204
+ end
205
+
206
+ context 'with DB backend' do
207
+ before(:each) do
208
+ Deimos.configure do |config|
209
+ config.producers.backend = :db
210
+ end
211
+ setup_db(DB_OPTIONS.last) # sqlite
212
+ allow(Deimos::Producer).to receive(:produce_batch).and_call_original
213
+ end
214
+
215
+ it 'should save to the DB' do
216
+ Widget.create!(widget_id: 1, name: 'widget')
217
+ expect(Deimos::KafkaMessage.count).to eq(2) # 2 producers
218
+ end
219
+
220
+ it 'should not save with a rollback' do
221
+ Widget.transaction do
222
+ Widget.create!(widget_id: 1, name: 'widget')
223
+ raise ActiveRecord::Rollback
224
+ end
225
+ expect(Deimos::KafkaMessage.count).to eq(0)
226
+ end
227
+ end
228
+
229
+ context 'with import hooks disabled' do
230
+ before(:each) do
231
+ # Dummy class we can include the mixin in. Has a backing table created
232
+ # earlier and has the import hook disabled
233
+ class WidgetNoImportHook < ActiveRecord::Base
234
+ include Deimos::KafkaSource
235
+ self.table_name = 'widgets'
236
+
237
+ # :nodoc:
238
+ def self.kafka_config
239
+ {
240
+ update: true,
241
+ delete: true,
242
+ import: false,
243
+ create: true
244
+ }
245
+ end
246
+
247
+ # :nodoc:
248
+ def self.kafka_producers
249
+ [WidgetProducer]
250
+ end
251
+ end
252
+ WidgetNoImportHook.reset_column_information
253
+ end
254
+
255
+ it 'should not fail when bulk-importing with existing records' do
256
+ widget1 = WidgetNoImportHook.create(widget_id: 1, name: 'Widget 1')
257
+ widget2 = WidgetNoImportHook.create(widget_id: 2, name: 'Widget 2')
258
+ widget1.name = 'New Widget No Import Hook 1'
259
+ widget2.name = 'New Widget No Import Hook 2'
260
+
261
+ expect {
262
+ WidgetNoImportHook.import([widget1, widget2], on_duplicate_key_update: %i(widget_id name))
263
+ }.not_to raise_error
264
+
265
+ expect('my-topic').not_to have_sent({
266
+ widget_id: 1,
267
+ name: 'New Widget No Import Hook 1',
268
+ id: widget1.id,
269
+ created_at: anything,
270
+ updated_at: anything
271
+ }, widget1.id)
272
+ expect('my-topic').not_to have_sent({
273
+ widget_id: 2,
274
+ name: 'New Widget No Import Hook 2',
275
+ id: widget2.id,
276
+ created_at: anything,
277
+ updated_at: anything
278
+ }, widget2.id)
279
+ end
280
+
281
+ it 'should not fail when mixing existing and new records' do
282
+ widget1 = WidgetNoImportHook.create(widget_id: 1, name: 'Widget 1')
283
+ expect('my-topic').to have_sent({
284
+ widget_id: 1,
285
+ name: 'Widget 1',
286
+ id: widget1.id,
287
+ created_at: anything,
288
+ updated_at: anything
289
+ }, widget1.id)
290
+
291
+ widget2 = WidgetNoImportHook.new(widget_id: 2, name: 'Widget 2')
292
+ widget1.name = 'New Widget 1'
293
+ WidgetNoImportHook.import([widget1, widget2], on_duplicate_key_update: %i(widget_id))
294
+ widgets = WidgetNoImportHook.all
295
+ expect('my-topic').not_to have_sent({
296
+ widget_id: 1,
297
+ name: 'New Widget 1',
298
+ id: widgets[0].id,
299
+ created_at: anything,
300
+ updated_at: anything
301
+ }, widgets[0].id)
302
+ expect('my-topic').not_to have_sent({
303
+ widget_id: 2,
304
+ name: 'Widget 2',
305
+ id: widgets[1].id,
306
+ created_at: anything,
307
+ updated_at: anything
308
+ }, widgets[1].id)
309
+ end
310
+ end
311
+
312
+ context 'with AR models that implement the kafka_producer interface' do
313
+ before(:each) do
314
+ # Dummy class we can include the mixin in. Has a backing table created
315
+ # earlier and has the import hook disabled
316
+ deprecated_class = Class.new(ActiveRecord::Base) do
317
+ include Deimos::KafkaSource
318
+ self.table_name = 'widgets'
319
+
320
+ # :nodoc:
321
+ def self.kafka_config
322
+ {
323
+ update: true,
324
+ delete: true,
325
+ import: false,
326
+ create: true
327
+ }
328
+ end
329
+
330
+ # :nodoc:
331
+ def self.kafka_producer
332
+ WidgetProducer
333
+ end
334
+ end
335
+ stub_const('WidgetDeprecated', deprecated_class)
336
+ WidgetDeprecated.reset_column_information
337
+ end
338
+
339
+ it 'logs a warning and sends the message as usual' do
340
+ expect(Deimos.config.logger).to receive(:warn).with({ message: WidgetDeprecated::DEPRECATION_WARNING })
341
+ widget = WidgetDeprecated.create(widget_id: 1, name: 'Widget 1')
342
+ expect('my-topic').to have_sent({
343
+ widget_id: 1,
344
+ name: 'Widget 1',
345
+ id: widget.id,
346
+ created_at: anything,
347
+ updated_at: anything
348
+ }, widget.id)
349
+ end
350
+ end
351
+
352
+ context 'with AR models that do not implement any producer interface' do
353
+ before(:each) do
354
+ # Dummy class we can include the mixin in. Has a backing table created
355
+ # earlier and has the import hook disabled
356
+ buggy_class = Class.new(ActiveRecord::Base) do
357
+ include Deimos::KafkaSource
358
+ self.table_name = 'widgets'
359
+
360
+ # :nodoc:
361
+ def self.kafka_config
362
+ {
363
+ update: true,
364
+ delete: true,
365
+ import: false,
366
+ create: true
367
+ }
368
+ end
369
+ end
370
+ stub_const('WidgetBuggy', buggy_class)
371
+ WidgetBuggy.reset_column_information
372
+ end
373
+
374
+ it 'raises a NotImplementedError exception' do
375
+ expect {
376
+ WidgetBuggy.create(widget_id: 1, name: 'Widget 1')
377
+ }.to raise_error(NotImplementedError)
378
+ end
379
+ end
380
+ end
381
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ each_db_config(Deimos::KafkaTopicInfo) do
4
+
5
+ it 'should lock the topic' do
6
+ expect(described_class.lock('my-topic', 'abc')).to be_truthy
7
+ expect(described_class.lock('my-topic', 'def')).to be_falsey
8
+ expect(described_class.lock('my-topic2', 'def')).to be_truthy
9
+ expect(described_class.count).to eq(2)
10
+ expect(described_class.first.locked_by).to eq('abc')
11
+ expect(described_class.last.locked_by).to eq('def')
12
+ end
13
+
14
+ it "should lock the topic if it's old" do
15
+ described_class.create!(topic: 'my-topic', locked_by: 'abc', error: true,
16
+ locked_at: 2.minutes.ago)
17
+ expect(described_class.lock('my-topic', 'abc')).to be_truthy
18
+ expect(described_class.count).to eq(1)
19
+ expect(described_class.first.locked_by).to eq('abc')
20
+
21
+ end
22
+
23
+ it "should lock the topic if it's not currently locked" do
24
+ described_class.create!(topic: 'my-topic', locked_by: nil,
25
+ locked_at: nil)
26
+ expect(described_class.lock('my-topic', 'abc')).to be_truthy
27
+ expect(described_class.count).to eq(1)
28
+ expect(described_class.first.locked_by).to eq('abc')
29
+ end
30
+
31
+ it "should not lock the topic if it's errored" do
32
+ described_class.create!(topic: 'my-topic', locked_by: nil,
33
+ locked_at: nil, error: true)
34
+ expect(described_class.lock('my-topic', 'abc')).to be_falsey
35
+ expect(described_class.count).to eq(1)
36
+ expect(described_class.first.locked_by).to eq(nil)
37
+ end
38
+
39
+ specify '#clear_lock' do
40
+ freeze_time do
41
+ Deimos::KafkaTopicInfo.create!(topic: 'my-topic', locked_by: 'abc',
42
+ locked_at: 10.seconds.ago, error: true, retries: 1,
43
+ last_processed_at: 20.seconds.ago)
44
+ Deimos::KafkaTopicInfo.create!(topic: 'my-topic2', locked_by: 'def',
45
+ locked_at: 10.seconds.ago, error: true, retries: 1,
46
+ last_processed_at: 20.seconds.ago)
47
+ Deimos::KafkaTopicInfo.clear_lock('my-topic', 'abc')
48
+ expect(Deimos::KafkaTopicInfo.count).to eq(2)
49
+ record = Deimos::KafkaTopicInfo.first
50
+ expect(record.locked_by).to eq(nil)
51
+ expect(record.locked_at).to eq(nil)
52
+ expect(record.error).to eq(false)
53
+ expect(record.retries).to eq(0)
54
+ expect(record.last_processed_at.in_time_zone.to_s).to eq(Time.zone.now.to_s)
55
+ record = Deimos::KafkaTopicInfo.last
56
+ expect(record.locked_by).not_to eq(nil)
57
+ expect(record.locked_at).not_to eq(nil)
58
+ expect(record.error).not_to eq(false)
59
+ expect(record.retries).not_to eq(0)
60
+ expect(record.last_processed_at.in_time_zone.to_s).to eq(20.seconds.ago.to_s)
61
+ end
62
+ end
63
+
64
+ specify '#ping_empty_topics' do
65
+ freeze_time do
66
+ old_time = 1.hour.ago.to_s
67
+ t1 = Deimos::KafkaTopicInfo.create!(topic: 'topic1', last_processed_at: old_time)
68
+ t2 = Deimos::KafkaTopicInfo.create!(topic: 'topic2', last_processed_at: old_time)
69
+ t3 = Deimos::KafkaTopicInfo.create!(topic: 'topic3', last_processed_at: old_time,
70
+ locked_by: 'me', locked_at: 1.minute.ago)
71
+
72
+ expect(Deimos::KafkaTopicInfo.count).to eq(3)
73
+ Deimos::KafkaTopicInfo.all.each { |t| expect(t.last_processed_at.in_time_zone.to_s).to eq(old_time) }
74
+ Deimos::KafkaTopicInfo.ping_empty_topics(%w(topic1))
75
+ expect(t1.reload.last_processed_at.in_time_zone.to_s).to eq(old_time) # was passed as an exception
76
+ expect(t2.reload.last_processed_at.in_time_zone.to_s).to eq(Time.zone.now.to_s)
77
+ expect(t3.reload.last_processed_at.in_time_zone.to_s).to eq(old_time) # is locked
78
+ end
79
+ end
80
+
81
+ specify '#register_error' do
82
+ freeze_time do
83
+ described_class.create!(topic: 'my-topic', locked_by: 'abc',
84
+ locked_at: 10.seconds.ago)
85
+ described_class.create!(topic: 'my-topic2', locked_by: 'def',
86
+ locked_at: 10.seconds.ago, error: true, retries: 1)
87
+ described_class.register_error('my-topic', 'abc')
88
+ record = described_class.first
89
+ expect(record.locked_by).to be_nil
90
+ expect(record.locked_at).to eq(Time.zone.now)
91
+ expect(record.error).to be_truthy
92
+ expect(record.retries).to eq(1)
93
+
94
+ described_class.register_error('my-topic2', 'def')
95
+ record = described_class.last
96
+ expect(record.error).to be_truthy
97
+ expect(record.retries).to eq(2)
98
+ expect(record.locked_at).to eq(Time.zone.now)
99
+ end
100
+ end
101
+
102
+ specify '#heartbeat' do
103
+ freeze_time do
104
+ described_class.create!(topic: 'my-topic', locked_by: 'abc',
105
+ locked_at: 10.seconds.ago)
106
+ described_class.heartbeat('my-topic', 'abc')
107
+ expect(described_class.last.locked_at).to eq(Time.zone.now)
108
+ end
109
+ end
110
+
111
+ end