deimos-temp-fork 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +83 -0
- data/.gitignore +41 -0
- data/.gitmodules +0 -0
- data/.rspec +1 -0
- data/.rubocop.yml +333 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +349 -0
- data/CODE_OF_CONDUCT.md +77 -0
- data/Dockerfile +23 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +286 -0
- data/Guardfile +22 -0
- data/LICENSE.md +195 -0
- data/README.md +1099 -0
- data/Rakefile +13 -0
- data/bin/deimos +4 -0
- data/deimos-ruby.gemspec +44 -0
- data/docker-compose.yml +71 -0
- data/docs/ARCHITECTURE.md +140 -0
- data/docs/CONFIGURATION.md +236 -0
- data/docs/DATABASE_BACKEND.md +147 -0
- data/docs/INTEGRATION_TESTS.md +52 -0
- data/docs/PULL_REQUEST_TEMPLATE.md +35 -0
- data/docs/UPGRADING.md +128 -0
- data/lib/deimos-temp-fork.rb +95 -0
- data/lib/deimos/active_record_consume/batch_consumption.rb +164 -0
- data/lib/deimos/active_record_consume/batch_slicer.rb +27 -0
- data/lib/deimos/active_record_consume/message_consumption.rb +79 -0
- data/lib/deimos/active_record_consume/schema_model_converter.rb +52 -0
- data/lib/deimos/active_record_consumer.rb +67 -0
- data/lib/deimos/active_record_producer.rb +87 -0
- data/lib/deimos/backends/base.rb +32 -0
- data/lib/deimos/backends/db.rb +41 -0
- data/lib/deimos/backends/kafka.rb +33 -0
- data/lib/deimos/backends/kafka_async.rb +33 -0
- data/lib/deimos/backends/test.rb +20 -0
- data/lib/deimos/batch_consumer.rb +7 -0
- data/lib/deimos/config/configuration.rb +381 -0
- data/lib/deimos/config/phobos_config.rb +137 -0
- data/lib/deimos/consume/batch_consumption.rb +150 -0
- data/lib/deimos/consume/message_consumption.rb +94 -0
- data/lib/deimos/consumer.rb +104 -0
- data/lib/deimos/instrumentation.rb +76 -0
- data/lib/deimos/kafka_message.rb +60 -0
- data/lib/deimos/kafka_source.rb +128 -0
- data/lib/deimos/kafka_topic_info.rb +102 -0
- data/lib/deimos/message.rb +79 -0
- data/lib/deimos/metrics/datadog.rb +47 -0
- data/lib/deimos/metrics/mock.rb +39 -0
- data/lib/deimos/metrics/provider.rb +36 -0
- data/lib/deimos/monkey_patches/phobos_cli.rb +35 -0
- data/lib/deimos/monkey_patches/phobos_producer.rb +51 -0
- data/lib/deimos/poll_info.rb +9 -0
- data/lib/deimos/producer.rb +224 -0
- data/lib/deimos/railtie.rb +8 -0
- data/lib/deimos/schema_backends/avro_base.rb +140 -0
- data/lib/deimos/schema_backends/avro_local.rb +30 -0
- data/lib/deimos/schema_backends/avro_schema_coercer.rb +119 -0
- data/lib/deimos/schema_backends/avro_schema_registry.rb +34 -0
- data/lib/deimos/schema_backends/avro_validation.rb +21 -0
- data/lib/deimos/schema_backends/base.rb +150 -0
- data/lib/deimos/schema_backends/mock.rb +42 -0
- data/lib/deimos/shared_config.rb +63 -0
- data/lib/deimos/test_helpers.rb +360 -0
- data/lib/deimos/tracing/datadog.rb +35 -0
- data/lib/deimos/tracing/mock.rb +40 -0
- data/lib/deimos/tracing/provider.rb +29 -0
- data/lib/deimos/utils/db_poller.rb +150 -0
- data/lib/deimos/utils/db_producer.rb +243 -0
- data/lib/deimos/utils/deadlock_retry.rb +68 -0
- data/lib/deimos/utils/inline_consumer.rb +150 -0
- data/lib/deimos/utils/lag_reporter.rb +175 -0
- data/lib/deimos/utils/schema_controller_mixin.rb +115 -0
- data/lib/deimos/version.rb +5 -0
- data/lib/generators/deimos/active_record/templates/migration.rb.tt +28 -0
- data/lib/generators/deimos/active_record/templates/model.rb.tt +5 -0
- data/lib/generators/deimos/active_record_generator.rb +79 -0
- data/lib/generators/deimos/db_backend/templates/migration +25 -0
- data/lib/generators/deimos/db_backend/templates/rails3_migration +31 -0
- data/lib/generators/deimos/db_backend_generator.rb +48 -0
- data/lib/generators/deimos/db_poller/templates/migration +11 -0
- data/lib/generators/deimos/db_poller/templates/rails3_migration +16 -0
- data/lib/generators/deimos/db_poller_generator.rb +48 -0
- data/lib/tasks/deimos.rake +34 -0
- data/spec/active_record_batch_consumer_spec.rb +481 -0
- data/spec/active_record_consume/batch_slicer_spec.rb +42 -0
- data/spec/active_record_consume/schema_model_converter_spec.rb +105 -0
- data/spec/active_record_consumer_spec.rb +154 -0
- data/spec/active_record_producer_spec.rb +85 -0
- data/spec/backends/base_spec.rb +10 -0
- data/spec/backends/db_spec.rb +54 -0
- data/spec/backends/kafka_async_spec.rb +11 -0
- data/spec/backends/kafka_spec.rb +11 -0
- data/spec/batch_consumer_spec.rb +256 -0
- data/spec/config/configuration_spec.rb +248 -0
- data/spec/consumer_spec.rb +209 -0
- data/spec/deimos_spec.rb +169 -0
- data/spec/generators/active_record_generator_spec.rb +56 -0
- data/spec/handlers/my_batch_consumer.rb +10 -0
- data/spec/handlers/my_consumer.rb +10 -0
- data/spec/kafka_listener_spec.rb +55 -0
- data/spec/kafka_source_spec.rb +381 -0
- data/spec/kafka_topic_info_spec.rb +111 -0
- data/spec/message_spec.rb +19 -0
- data/spec/phobos.bad_db.yml +73 -0
- data/spec/phobos.yml +77 -0
- data/spec/producer_spec.rb +498 -0
- data/spec/rake_spec.rb +19 -0
- data/spec/schema_backends/avro_base_shared.rb +199 -0
- data/spec/schema_backends/avro_local_spec.rb +32 -0
- data/spec/schema_backends/avro_schema_registry_spec.rb +32 -0
- data/spec/schema_backends/avro_validation_spec.rb +24 -0
- data/spec/schema_backends/base_spec.rb +33 -0
- data/spec/schemas/com/my-namespace/Generated.avsc +71 -0
- data/spec/schemas/com/my-namespace/MyNestedSchema.avsc +62 -0
- data/spec/schemas/com/my-namespace/MySchema-key.avsc +13 -0
- data/spec/schemas/com/my-namespace/MySchema.avsc +18 -0
- data/spec/schemas/com/my-namespace/MySchemaCompound-key.avsc +18 -0
- data/spec/schemas/com/my-namespace/MySchemaWithBooleans.avsc +18 -0
- data/spec/schemas/com/my-namespace/MySchemaWithDateTimes.avsc +33 -0
- data/spec/schemas/com/my-namespace/MySchemaWithId.avsc +28 -0
- data/spec/schemas/com/my-namespace/MySchemaWithUniqueId.avsc +32 -0
- data/spec/schemas/com/my-namespace/Wibble.avsc +43 -0
- data/spec/schemas/com/my-namespace/Widget.avsc +27 -0
- data/spec/schemas/com/my-namespace/WidgetTheSecond.avsc +27 -0
- data/spec/schemas/com/my-namespace/request/CreateTopic.avsc +11 -0
- data/spec/schemas/com/my-namespace/request/Index.avsc +11 -0
- data/spec/schemas/com/my-namespace/request/UpdateRequest.avsc +11 -0
- data/spec/schemas/com/my-namespace/response/CreateTopic.avsc +11 -0
- data/spec/schemas/com/my-namespace/response/Index.avsc +11 -0
- data/spec/schemas/com/my-namespace/response/UpdateResponse.avsc +11 -0
- data/spec/spec_helper.rb +267 -0
- data/spec/utils/db_poller_spec.rb +320 -0
- data/spec/utils/db_producer_spec.rb +514 -0
- data/spec/utils/deadlock_retry_spec.rb +74 -0
- data/spec/utils/inline_consumer_spec.rb +31 -0
- data/spec/utils/lag_reporter_spec.rb +76 -0
- data/spec/utils/platform_schema_validation_spec.rb +0 -0
- data/spec/utils/schema_controller_mixin_spec.rb +84 -0
- data/support/deimos-solo.png +0 -0
- data/support/deimos-with-name-next.png +0 -0
- data/support/deimos-with-name.png +0 -0
- data/support/flipp-logo.png +0 -0
- 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
|