deimos-temp-fork 0.0.1

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