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,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Deimos::ActiveRecordConsume::BatchSlicer do
4
+ describe '#slice' do
5
+ let(:batch) do
6
+ [
7
+ Deimos::Message.new({ v: 1 }, nil, key: 'C'),
8
+ Deimos::Message.new({ v: 123 }, nil, key: 'A'),
9
+ Deimos::Message.new({ v: 999 }, nil, key: 'B'),
10
+ Deimos::Message.new({ v: 456 }, nil, key: 'A'),
11
+ Deimos::Message.new({ v: 2 }, nil, key: 'C'),
12
+ Deimos::Message.new({ v: 3 }, nil, key: 'C')
13
+ ]
14
+ end
15
+
16
+ it 'should slice a batch by key' do
17
+ slices = described_class.slice(batch)
18
+
19
+ expect(slices).
20
+ to match([
21
+ match_array([
22
+ Deimos::Message.new({ v: 1 }, nil, key: 'C'),
23
+ Deimos::Message.new({ v: 123 }, nil, key: 'A'),
24
+ Deimos::Message.new({ v: 999 }, nil, key: 'B')
25
+ ]),
26
+ match_array([
27
+ Deimos::Message.new({ v: 456 }, nil, key: 'A'),
28
+ Deimos::Message.new({ v: 2 }, nil, key: 'C')
29
+ ]),
30
+ match_array([
31
+ Deimos::Message.new({ v: 3 }, nil, key: 'C')
32
+ ])
33
+ ])
34
+ end
35
+
36
+ it 'should handle empty batches' do
37
+ slices = described_class.slice([])
38
+
39
+ expect(slices).to be_empty
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deimos/active_record_consume/schema_model_converter'
4
+ require 'deimos/schema_backends/avro_local'
5
+
6
+ # Wrapped in a module to prevent class leakage
7
+ module SchemaModelConverterTest
8
+ describe Deimos::ActiveRecordConsume::SchemaModelConverter do
9
+ # Create ActiveRecord table and model
10
+ before(:all) do
11
+ ActiveRecord::Base.connection.create_table(:wibbles, force: true) do |t|
12
+ t.integer(:wibble_id)
13
+ t.string(:name)
14
+ t.integer(:bar)
15
+ t.datetime(:birthday_int)
16
+ t.datetime(:birthday_long)
17
+ t.datetime(:birthday_optional)
18
+ t.timestamps
19
+ end
20
+
21
+ # :nodoc:
22
+ class Wibble < ActiveRecord::Base
23
+ end
24
+ Wibble.reset_column_information
25
+ end
26
+
27
+ after(:all) do
28
+ ActiveRecord::Base.connection.drop_table(:wibbles)
29
+ end
30
+
31
+ let(:schema) { Deimos::SchemaBackends::AvroLocal.new(schema: 'Wibble', namespace: 'com.my-namespace') }
32
+ let(:inst) { described_class.new(schema, Wibble) }
33
+
34
+ describe '#convert' do
35
+ it 'should extract attributes from the payload' do
36
+ payload = { 'id' => 123, 'wibble_id' => 456, 'name' => 'wibble' }
37
+
38
+ expect(inst.convert(payload)).to include('id' => 123, 'wibble_id' => 456, 'name' => 'wibble')
39
+ end
40
+
41
+ it 'should ignore payload fields that don\'t exist' do
42
+ payload = { 'foo' => 'abc' }
43
+
44
+ expect(inst.convert(payload)).not_to include('foo')
45
+ end
46
+
47
+ it 'should ignore fields in the schema but not in the model' do
48
+ payload = { 'floop' => 'def' }
49
+
50
+ expect(inst.convert(payload)).not_to include('floop')
51
+ end
52
+
53
+ it 'should ignore model fields not in the schema' do
54
+ payload = { 'bar' => 'xyz' }
55
+
56
+ expect(inst.convert(payload)).not_to include('bar')
57
+ end
58
+
59
+ it 'should handle nils' do
60
+ payload = { 'name' => nil }
61
+
62
+ expect(inst.convert(payload)['name']).to be_nil
63
+ end
64
+
65
+ describe 'timestamps' do
66
+ it 'should ignore AR timestamp fields' do
67
+ payload = { 'updated_at' => '1234567890', 'created_at' => '2345678901' }
68
+
69
+ expect(inst.convert(payload)).not_to include('updated_at', 'created_at')
70
+ end
71
+
72
+ it 'should parse timestamps' do
73
+ first = Time.zone.local(2019, 1, 1, 11, 12, 13)
74
+ second = Time.zone.local(2019, 2, 2, 12, 13, 14)
75
+
76
+ payload = { 'birthday_int' => first.to_i, 'birthday_long' => second.to_i }
77
+
78
+ expect(inst.convert(payload)).to include('birthday_int' => first, 'birthday_long' => second)
79
+ end
80
+
81
+ it 'should parse integer-string timestamps' do
82
+ date = Time.zone.local(2019, 1, 1, 11, 12, 13)
83
+
84
+ payload = { 'birthday_int' => '1546359133' }
85
+
86
+ expect(inst.convert(payload)).to include('birthday_int' => date)
87
+ end
88
+
89
+ it 'should ignore other strings for timestamps' do
90
+ payload = { 'birthday_int' => 'some-other-val' }
91
+
92
+ expect(inst.convert(payload)).to include('birthday_int' => 'some-other-val')
93
+ end
94
+ end
95
+
96
+ it 'should coerce nullable unions' do
97
+ date = Time.zone.local(2019, 1, 1, 11, 12, 13)
98
+
99
+ payload = { 'birthday_optional' => date.to_i }
100
+
101
+ expect(inst.convert(payload)).to include('birthday_optional' => date)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ # Wrapped in a module to prevent class leakage
6
+ module ActiveRecordConsumerTest
7
+ describe Deimos::ActiveRecordConsumer, 'Message Consumer' do
8
+
9
+ before(:all) do
10
+ ActiveRecord::Base.connection.create_table(:widgets, force: true) do |t|
11
+ t.string(:test_id)
12
+ t.integer(:some_int)
13
+ t.boolean(:some_bool)
14
+ t.datetime(:some_datetime_int)
15
+ t.timestamps
16
+ end
17
+
18
+ # :nodoc:
19
+ class Widget < ActiveRecord::Base
20
+ default_scope -> { where(some_bool: false) }
21
+ end
22
+ Widget.reset_column_information
23
+ end
24
+
25
+ before(:each) do
26
+ Widget.delete_all
27
+ end
28
+
29
+ after(:all) do
30
+ ActiveRecord::Base.connection.drop_table(:widgets)
31
+ end
32
+
33
+ prepend_before(:each) do
34
+
35
+ consumer_class = Class.new(Deimos::ActiveRecordConsumer) do
36
+ schema 'MySchemaWithDateTimes'
37
+ namespace 'com.my-namespace'
38
+ key_config plain: true
39
+ record_class Widget
40
+ end
41
+ stub_const('MyConsumer', consumer_class)
42
+
43
+ consumer_class = Class.new(Deimos::ActiveRecordConsumer) do
44
+ schema 'MySchema'
45
+ namespace 'com.my-namespace'
46
+ key_config none: true
47
+ record_class Widget
48
+
49
+ # :nodoc:
50
+ def assign_key(_record, _payload, _key)
51
+ # do nothing since we're not using primary keys
52
+ end
53
+
54
+ # :nodoc:
55
+ def fetch_record(klass, payload, _key)
56
+ klass.unscoped.where('test_id' => payload[:test_id]).first
57
+ end
58
+ end
59
+ stub_const('MyCustomFetchConsumer', consumer_class)
60
+
61
+ Time.zone = 'Eastern Time (US & Canada)'
62
+ end
63
+
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
+
104
+ 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
+ end
153
+ end
154
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Deimos::ActiveRecordProducer do
4
+
5
+ include_context 'with widgets'
6
+
7
+ prepend_before(:each) do
8
+
9
+ producer_class = Class.new(Deimos::ActiveRecordProducer) do
10
+ schema 'MySchema'
11
+ namespace 'com.my-namespace'
12
+ topic 'my-topic'
13
+ key_config none: true
14
+ end
15
+ stub_const('MyProducer', producer_class)
16
+
17
+ producer_class = Class.new(Deimos::ActiveRecordProducer) do
18
+ schema 'MySchemaWithBooleans'
19
+ namespace 'com.my-namespace'
20
+ topic 'my-topic-with-boolean'
21
+ key_config none: true
22
+ end
23
+ stub_const('MyBooleanProducer', producer_class)
24
+
25
+ producer_class = Class.new(Deimos::ActiveRecordProducer) do
26
+ schema 'MySchemaWithId'
27
+ namespace 'com.my-namespace'
28
+ topic 'my-topic-with-id'
29
+ key_config none: true
30
+ record_class Widget
31
+
32
+ # :nodoc:
33
+ def self.generate_payload(attrs, widget)
34
+ super.merge(message_id: widget.generated_id)
35
+ end
36
+
37
+ end
38
+ stub_const('MyProducerWithID', producer_class)
39
+
40
+ producer_class = Class.new(Deimos::ActiveRecordProducer) do
41
+ schema 'MySchemaWithUniqueId'
42
+ namespace 'com.my-namespace'
43
+ topic 'my-topic-with-unique-id'
44
+ key_config field: :id
45
+ record_class Widget
46
+ end
47
+ stub_const('MyProducerWithUniqueID', producer_class)
48
+ end
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
54
+
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
69
+
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
+ )
79
+ end
80
+
81
+ specify '#watched_attributes' do
82
+ expect(MyProducer.watched_attributes).to eq(%w(test_id some_int))
83
+ end
84
+
85
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Deimos::Backends::Base do
4
+ include_context 'with publish_backend'
5
+ it 'should call execute' do
6
+ expect(described_class).to receive(:execute).
7
+ with(messages: messages, producer_class: MyProducer)
8
+ described_class.publish(producer_class: MyProducer, messages: messages)
9
+ end
10
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ each_db_config(Deimos::Backends::Db) do
4
+ include_context 'with publish_backend'
5
+
6
+ it 'should save to the database' do
7
+ expect(Deimos.config.metrics).to receive(:increment).with(
8
+ 'db_producer.insert',
9
+ tags: %w(topic:my-topic),
10
+ by: 3
11
+ )
12
+ described_class.publish(producer_class: MyProducer, messages: messages)
13
+ records = Deimos::KafkaMessage.all
14
+ expect(records.size).to eq(3)
15
+ expect(records[0].attributes.to_h).to include(
16
+ 'message' => '{"foo"=>1}',
17
+ 'topic' => 'my-topic',
18
+ 'key' => 'foo1'
19
+ )
20
+ expect(records[1].attributes.to_h).to include(
21
+ 'message' => '{"foo"=>2}',
22
+ 'topic' => 'my-topic',
23
+ 'key' => 'foo2'
24
+ )
25
+ expect(records[2].attributes.to_h).to include(
26
+ 'message' => '{"foo"=>3}',
27
+ 'topic' => 'my-topic',
28
+ 'key' => 'foo3'
29
+ )
30
+ end
31
+
32
+ it 'should add nil messages' do
33
+ described_class.publish(producer_class: MyProducer,
34
+ messages: [build_message(nil, 'my-topic', 'foo1')])
35
+ expect(Deimos::KafkaMessage.count).to eq(1)
36
+ expect(Deimos::KafkaMessage.last.message).to eq(nil)
37
+ end
38
+
39
+ it 'should add to non-keyed messages' do
40
+ described_class.publish(producer_class: MyNoKeyProducer,
41
+ messages: messages)
42
+ expect(Deimos::KafkaMessage.count).to eq(3)
43
+ described_class.publish(producer_class: MyNoKeyProducer,
44
+ messages: [messages.first])
45
+ expect(Deimos::KafkaMessage.count).to eq(4)
46
+ end
47
+
48
+ it 'should add messages with Hash keys with JSON encoding' do
49
+ described_class.publish(producer_class: MyProducer,
50
+ messages: [build_message({ foo: 0 }, 'my-topic', { 'test_id' => 0 })])
51
+ expect(Deimos::KafkaMessage.count).to eq(1)
52
+ expect(Deimos::KafkaMessage.last.partition_key).to eq(%(---\ntest_id: 0\n))
53
+ end
54
+ end