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
+ module Deimos
4
+ module SchemaBackends
5
+ # Mock implementation of a schema backend that does no encoding or validation.
6
+ class Mock < Base
7
+ # @override
8
+ def decode_payload(payload, schema:)
9
+ payload.is_a?(String) ? 'payload-decoded' : payload.map { |k, v| [k, "decoded-#{v}"] }
10
+ end
11
+
12
+ # @override
13
+ def encode_payload(payload, schema:, topic: nil)
14
+ payload.is_a?(String) ? 'payload-encoded' : payload.map { |k, v| [k, "encoded-#{v}"] }
15
+ end
16
+
17
+ # @override
18
+ def validate(_payload, schema:)
19
+ end
20
+
21
+ # @override
22
+ def schema_fields
23
+ []
24
+ end
25
+
26
+ # @override
27
+ def coerce_field(_type, value)
28
+ value
29
+ end
30
+
31
+ # @override
32
+ def encode_key(key_id, key)
33
+ { key_id => key }
34
+ end
35
+
36
+ # @override
37
+ def decode_key(payload, key_id)
38
+ payload[key_id]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module Deimos
6
+ # Module that producers and consumers can share which sets up configuration.
7
+ module SharedConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ # need to use this instead of class_methods to be backwards-compatible
11
+ # with Rails 3
12
+ module ClassMethods
13
+ # @return [Hash]
14
+ def config
15
+ return @config if @config
16
+
17
+ # default to none: true
18
+ @config = {
19
+ key_configured: false,
20
+ encode_key: false,
21
+ no_keys: true
22
+ }
23
+ klass = self.superclass
24
+ while klass.respond_to?(:config)
25
+ klass_config = klass.config
26
+ if klass_config
27
+ # default is true for this so don't include it in the merge
28
+ klass_config.delete(:encode_key) if klass_config[:encode_key]
29
+ @config.merge!(klass_config) if klass.config
30
+ end
31
+ klass = klass.superclass
32
+ end
33
+ @config
34
+ end
35
+
36
+ # Set the schema.
37
+ # @param schema [String]
38
+ def schema(schema)
39
+ config[:schema] = schema
40
+ end
41
+
42
+ # Set the namespace.
43
+ # @param namespace [String]
44
+ def namespace(namespace)
45
+ config[:namespace] = namespace
46
+ end
47
+
48
+ # Set key configuration.
49
+ # @param field [Symbol] the name of a field to use in the value schema as
50
+ # a generated key schema
51
+ # @param schema [String|Symbol] the name of a schema to use for the key
52
+ # @param plain [Boolean] if true, do not encode keys at all
53
+ # @param none [Boolean] if true, do not use keys at all
54
+ def key_config(plain: nil, field: nil, schema: nil, none: nil)
55
+ config[:key_configured] = true
56
+ config[:no_keys] = none
57
+ config[:encode_key] = !plain && !none
58
+ config[:key_field] = field&.to_s
59
+ config[:key_schema] = schema
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,360 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext'
5
+ require 'deimos/tracing/mock'
6
+ require 'deimos/metrics/mock'
7
+
8
+ module Deimos
9
+ # Include this module in your RSpec spec_helper
10
+ # to stub out external dependencies
11
+ # and add methods to use to test encoding/decoding.
12
+ module TestHelpers
13
+ extend ActiveSupport::Concern
14
+
15
+ class << self
16
+ # for backwards compatibility
17
+ # @return [Array<Hash>]
18
+ def sent_messages
19
+ Deimos::Backends::Test.sent_messages
20
+ end
21
+
22
+ # Set the config to the right settings for a unit test
23
+ def unit_test!
24
+ Deimos.configure do |deimos_config|
25
+ deimos_config.logger = Logger.new(STDOUT)
26
+ deimos_config.consumers.reraise_errors = true
27
+ deimos_config.kafka.seed_brokers ||= ['test_broker']
28
+ deimos_config.schema.backend = Deimos.schema_backend_class.mock_backend
29
+ deimos_config.producers.backend = :test
30
+ end
31
+ end
32
+
33
+ # Kafka test config with avro schema registry
34
+ def full_integration_test!
35
+ Deimos.configure do |deimos_config|
36
+ deimos_config.producers.backend = :kafka
37
+ deimos_config.schema.backend = :avro_schema_registry
38
+ end
39
+ end
40
+
41
+ # Set the config to the right settings for a kafka test
42
+ def kafka_test!
43
+ Deimos.configure do |deimos_config|
44
+ deimos_config.producers.backend = :kafka
45
+ deimos_config.schema.backend = :avro_validation
46
+ end
47
+ end
48
+ end
49
+
50
+ included do
51
+
52
+ RSpec.configure do |config|
53
+
54
+ config.prepend_before(:each) do
55
+ client = double('client').as_null_object
56
+ allow(client).to receive(:time) do |*_args, &block|
57
+ block.call
58
+ end
59
+ Deimos::Backends::Test.sent_messages.clear
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ # @deprecated
66
+ def stub_producers_and_consumers!
67
+ warn('stub_producers_and_consumers! is no longer necessary and this method will be removed in 3.0')
68
+ end
69
+
70
+ # @deprecated
71
+ def stub_producer(_klass)
72
+ warn('Stubbing producers is no longer necessary and this method will be removed in 3.0')
73
+ end
74
+
75
+ # @deprecated
76
+ def stub_consumer(_klass)
77
+ warn('Stubbing consumers is no longer necessary and this method will be removed in 3.0')
78
+ end
79
+
80
+ # @deprecated
81
+ def stub_batch_consumer(_klass)
82
+ warn('Stubbing batch consumers is no longer necessary and this method will be removed in 3.0')
83
+ end
84
+
85
+ # get the difference of 2 hashes.
86
+ # @param hash1 [Hash]
87
+ # @param hash2 [Hash]
88
+ def _hash_diff(hash1, hash2)
89
+ if hash1.nil? || !hash1.is_a?(Hash)
90
+ hash2
91
+ elsif hash2.nil? || !hash2.is_a?(Hash)
92
+ hash1
93
+ else
94
+ hash1.dup.
95
+ delete_if { |k, v| hash2[k] == v }.
96
+ merge!(hash2.dup.delete_if { |k, _v| hash1.key?(k) })
97
+ end
98
+ end
99
+
100
+ # :nodoc:
101
+ def _frk_failure_message(topic, message, key=nil, partition_key=nil, was_negated=false)
102
+ messages = Deimos::Backends::Test.sent_messages.
103
+ select { |m| m[:topic] == topic }.
104
+ map { |m| m.except(:topic) }
105
+ message_string = ''
106
+ diff = nil
107
+ min_hash_diff = nil
108
+ if messages.any?
109
+ message_string = messages.map(&:inspect).join("\n")
110
+ min_hash_diff = messages.min_by { |m| _hash_diff(m, message).keys.size }
111
+ diff = RSpec::Expectations.differ.
112
+ diff_as_object(message, min_hash_diff[:payload])
113
+ end
114
+ description = if message.respond_to?(:description)
115
+ message.description
116
+ elsif message.nil?
117
+ 'nil'
118
+ else
119
+ message
120
+ end
121
+ str = "Expected #{topic} #{'not ' if was_negated}to have sent #{description}"
122
+ str += " with key #{key}" if key
123
+ str += " with partition key #{partition_key}" if partition_key
124
+ str += "\nClosest message received: #{min_hash_diff}" if min_hash_diff
125
+ str += "\nDiff: #{diff}" if diff
126
+ str + "\nAll Messages received:\n#{message_string}"
127
+ end
128
+
129
+ RSpec::Matchers.define :have_sent do |msg, key=nil, partition_key=nil|
130
+ message = if msg.respond_to?(:with_indifferent_access)
131
+ msg.with_indifferent_access
132
+ else
133
+ msg
134
+ end
135
+ match do |topic|
136
+ Deimos::Backends::Test.sent_messages.any? do |m|
137
+ hash_matcher = RSpec::Matchers::BuiltIn::Match.new(message)
138
+ hash_matcher.send(:match,
139
+ message,
140
+ m[:payload]&.with_indifferent_access) &&
141
+ topic == m[:topic] &&
142
+ (key.present? ? key == m[:key] : true) &&
143
+ (partition_key.present? ? partition_key == m[:partition_key] : true)
144
+ end
145
+ end
146
+
147
+ if respond_to?(:failure_message)
148
+ failure_message do |topic|
149
+ _frk_failure_message(topic, message, key, partition_key)
150
+ end
151
+ failure_message_when_negated do |topic|
152
+ _frk_failure_message(topic, message, key, partition_key, true)
153
+ end
154
+ else
155
+ failure_message_for_should do |topic|
156
+ _frk_failure_message(topic, message, key, partition_key)
157
+ end
158
+ failure_message_for_should_not do |topic|
159
+ _frk_failure_message(topic, message, key, partition_key, true)
160
+ end
161
+ end
162
+ end
163
+
164
+ # Clear all sent messages - e.g. if we want to check that
165
+ # particular messages were sent or not sent after a point in time.
166
+ def clear_kafka_messages!
167
+ Deimos::Backends::Test.sent_messages.clear
168
+ end
169
+
170
+ # Test that a given handler will consume a given payload correctly, i.e.
171
+ # that the schema is correct. If
172
+ # a block is given, that block will be executed when `consume` is called.
173
+ # Otherwise it will just confirm that `consume` is called at all.
174
+ # @param handler_class_or_topic [Class|String] Class which inherits from
175
+ # Deimos::Consumer or the topic as a string
176
+ # @param payload [Hash] the payload to consume
177
+ # @param call_original [Boolean] if true, allow the consume handler
178
+ # to continue as normal. Not compatible with a block.
179
+ # @param ignore_expectation [Boolean] Set to true to not place any
180
+ # expectations on the consumer. Primarily used internally to Deimos.
181
+ # @param key [Object] the key to use.
182
+ # @param partition_key [Object] the partition key to use.
183
+ def test_consume_message(handler_class_or_topic,
184
+ payload,
185
+ call_original: false,
186
+ key: nil,
187
+ partition_key: nil,
188
+ skip_expectation: false,
189
+ &block)
190
+ raise 'Cannot have both call_original and be given a block!' if call_original && block_given?
191
+
192
+ payload&.stringify_keys!
193
+ handler_class = if handler_class_or_topic.is_a?(String)
194
+ _get_handler_class_from_topic(handler_class_or_topic)
195
+ else
196
+ handler_class_or_topic
197
+ end
198
+ handler = handler_class.new
199
+ allow(handler_class).to receive(:new).and_return(handler)
200
+ listener = double('listener',
201
+ handler_class: handler_class,
202
+ encoding: nil)
203
+ key ||= _key_from_consumer(handler_class)
204
+ message = double('message',
205
+ 'key' => key,
206
+ 'partition_key' => partition_key,
207
+ 'partition' => 1,
208
+ 'offset' => 1,
209
+ 'headers' => {},
210
+ 'value' => payload)
211
+
212
+ unless skip_expectation
213
+ expectation = expect(handler).to receive(:consume).
214
+ with(payload, anything, &block)
215
+ expectation.and_call_original if call_original
216
+ end
217
+ Phobos::Actions::ProcessMessage.new(
218
+ listener: listener,
219
+ message: message,
220
+ listener_metadata: { topic: 'my-topic' }
221
+ ).send(:process_message, payload)
222
+ end
223
+
224
+ # Check to see that a given message will fail due to validation errors.
225
+ # @param handler_class [Class]
226
+ # @param payload [Hash]
227
+ def test_consume_invalid_message(handler_class, payload)
228
+ expect {
229
+ handler_class.decoder.validate(payload,
230
+ schema: handler_class.decoder.schema)
231
+ }.to raise_error(Avro::SchemaValidator::ValidationError)
232
+ end
233
+
234
+ # Test that a given handler will consume a given batch payload correctly,
235
+ # i.e. that the schema is correct. If
236
+ # a block is given, that block will be executed when `consume` is called.
237
+ # Otherwise it will just confirm that `consume` is called at all.
238
+ # @param handler_class_or_topic [Class|String] Class which inherits from
239
+ # Deimos::Consumer or the topic as a string
240
+ # @param payloads [Array<Hash>] the payload to consume
241
+ def test_consume_batch(handler_class_or_topic,
242
+ payloads,
243
+ keys: [],
244
+ partition_keys: [],
245
+ call_original: false,
246
+ skip_expectation: false,
247
+ &block)
248
+ if call_original && block_given?
249
+ raise 'Cannot have both call_original and be given a block!'
250
+ end
251
+
252
+ topic_name = 'my-topic'
253
+ handler_class = if handler_class_or_topic.is_a?(String)
254
+ _get_handler_class_from_topic(handler_class_or_topic)
255
+ else
256
+ handler_class_or_topic
257
+ end
258
+ handler = handler_class.new
259
+ allow(handler_class).to receive(:new).and_return(handler)
260
+ listener = double('listener',
261
+ handler_class: handler_class,
262
+ encoding: nil)
263
+ batch_messages = payloads.zip(keys, partition_keys).map do |payload, key, partition_key|
264
+ key ||= _key_from_consumer(handler_class)
265
+
266
+ double('message',
267
+ 'key' => key,
268
+ 'partition_key' => partition_key,
269
+ 'partition' => 1,
270
+ 'offset' => 1,
271
+ 'headers' => {},
272
+ 'value' => payload)
273
+ end
274
+ batch = double('fetched_batch',
275
+ 'messages' => batch_messages,
276
+ 'topic' => topic_name,
277
+ 'partition' => 1,
278
+ 'offset_lag' => 0)
279
+ unless skip_expectation
280
+ expectation = expect(handler).to receive(:consume_batch).
281
+ with(payloads, anything, &block)
282
+ expectation.and_call_original if call_original
283
+ end
284
+ action = Phobos::Actions::ProcessBatchInline.new(
285
+ listener: listener,
286
+ batch: batch,
287
+ metadata: { topic: topic_name }
288
+ )
289
+ allow(action).to receive(:backoff_interval).and_return(0)
290
+ allow(action).to receive(:handle_error) { |e| raise e }
291
+ action.send(:execute)
292
+ end
293
+
294
+ # Check to see that a given message will fail due to validation errors.
295
+ # @param handler_class [Class]
296
+ # @param payloads [Array<Hash>]
297
+ def test_consume_batch_invalid_message(handler_class, payloads)
298
+ topic_name = 'my-topic'
299
+ handler = handler_class.new
300
+ allow(handler_class).to receive(:new).and_return(handler)
301
+ listener = double('listener',
302
+ handler_class: handler_class,
303
+ encoding: nil)
304
+ batch_messages = payloads.map do |payload|
305
+ key ||= _key_from_consumer(handler_class)
306
+
307
+ double('message',
308
+ 'key' => key,
309
+ 'partition' => 1,
310
+ 'offset' => 1,
311
+ 'value' => payload)
312
+ end
313
+ batch = double('fetched_batch',
314
+ 'messages' => batch_messages,
315
+ 'topic' => topic_name,
316
+ 'partition' => 1,
317
+ 'offset_lag' => 0)
318
+
319
+ action = Phobos::Actions::ProcessBatchInline.new(
320
+ listener: listener,
321
+ batch: batch,
322
+ metadata: { topic: topic_name }
323
+ )
324
+ allow(action).to receive(:backoff_interval).and_return(0)
325
+ allow(action).to receive(:handle_error) { |e| raise e }
326
+
327
+ expect { action.send(:execute) }.
328
+ to raise_error
329
+ end
330
+
331
+ private
332
+
333
+ def _key_from_consumer(consumer)
334
+ if consumer.config[:key_field]
335
+ { consumer.config[:key_field] => 1 }
336
+ elsif consumer.config[:key_schema]
337
+ backend = consumer.decoder
338
+ old_schema = backend.schema
339
+ backend.schema = consumer.config[:key_schema]
340
+ key = backend.schema_fields.map { |field| [field.name, 1] }.to_h
341
+ backend.schema = old_schema
342
+ key
343
+ elsif consumer.config[:no_keys]
344
+ nil
345
+ else
346
+ 1
347
+ end
348
+ end
349
+
350
+ # @param topic [String]
351
+ # @return [Class]
352
+ def _get_handler_class_from_topic(topic)
353
+ listeners = Phobos.config['listeners']
354
+ handler = listeners.find { |l| l.topic == topic }
355
+ raise "No consumer found in Phobos configuration for topic #{topic}!" if handler.nil?
356
+
357
+ handler.handler.constantize
358
+ end
359
+ end
360
+ end