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.
- 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
|