nulogy_message_bus_consumer 0.3.0 → 0.5.0
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 +4 -4
- data/Rakefile +5 -4
- data/config/credentials/message-bus-us-east-1.key +1 -0
- data/config/credentials/message-bus-us-east-1.yml.enc +1 -0
- data/lib/nulogy_message_bus_consumer.rb +18 -6
- data/lib/nulogy_message_bus_consumer/clock.rb +13 -0
- data/lib/nulogy_message_bus_consumer/config.rb +12 -4
- data/lib/nulogy_message_bus_consumer/deployment/ecs.rb +23 -0
- data/lib/nulogy_message_bus_consumer/handlers/log_unprocessed_messages.rb +2 -1
- data/lib/nulogy_message_bus_consumer/kafka_utils.rb +2 -1
- data/lib/nulogy_message_bus_consumer/lag_tracker.rb +53 -0
- data/lib/nulogy_message_bus_consumer/message.rb +12 -5
- data/lib/nulogy_message_bus_consumer/null_logger.rb +6 -3
- data/lib/nulogy_message_bus_consumer/pipeline.rb +6 -3
- data/lib/nulogy_message_bus_consumer/steps/commit_on_success.rb +1 -0
- data/lib/nulogy_message_bus_consumer/steps/connect_to_message_bus.rb +27 -8
- data/lib/nulogy_message_bus_consumer/steps/deduplicate_messages.rb +1 -1
- data/lib/nulogy_message_bus_consumer/steps/{monitor_replication_lag.rb → log_consumer_lag.rb} +3 -3
- data/lib/nulogy_message_bus_consumer/steps/log_messages.rb +14 -3
- data/lib/nulogy_message_bus_consumer/steps/stream_messages.rb +2 -2
- data/lib/nulogy_message_bus_consumer/steps/stream_messages_until_none_are_left.rb +2 -2
- data/lib/nulogy_message_bus_consumer/steps/supervise_consumer_lag.rb +76 -0
- data/lib/nulogy_message_bus_consumer/version.rb +1 -1
- data/lib/tasks/engine/message_bus_consumer.rake +9 -10
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/javascript/packs/application.js +15 -0
- data/spec/dummy/app/jobs/application_job.rb +7 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +33 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +29 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/credentials/message-bus-us-east-1.key +1 -0
- data/spec/dummy/config/credentials/message-bus-us-east-1.yml.enc +1 -0
- data/spec/dummy/config/database.yml +27 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +62 -0
- data/spec/dummy/config/environments/production.rb +112 -0
- data/spec/dummy/config/environments/test.rb +49 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/assets.rb +12 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/message_bus_consumer.rb +5 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/puma.rb +36 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/dummy/db/schema.rb +21 -0
- data/spec/dummy/log/development.log +4 -0
- data/spec/dummy/log/production.log +18 -0
- data/spec/dummy/log/test.log +6083 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/tmp/development_secret.txt +1 -0
- data/spec/integration/nulogy_message_bus_consumer/auditor_spec.rb +59 -0
- data/spec/integration/nulogy_message_bus_consumer/kafka_utils_spec.rb +41 -0
- data/spec/integration/nulogy_message_bus_consumer/steps/commit_on_success_spec.rb +131 -0
- data/spec/integration/nulogy_message_bus_consumer/steps/connect_to_message_bus_spec.rb +54 -0
- data/spec/integration/nulogy_message_bus_consumer/steps/supervise_consumer_lag_spec.rb +54 -0
- data/spec/integration/test_topic_spec.rb +39 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/support/kafka.rb +74 -0
- data/spec/support/middleware_tap.rb +12 -0
- data/spec/support/test_topic.rb +48 -0
- data/spec/unit/nulogy_message_bus_consumer/config_spec.rb +20 -0
- data/spec/unit/nulogy_message_bus_consumer/lag_tracker.rb +35 -0
- data/spec/unit/nulogy_message_bus_consumer/message_spec.rb +84 -0
- data/spec/unit/nulogy_message_bus_consumer/pipeline_spec.rb +49 -0
- data/spec/unit/nulogy_message_bus_consumer/steps/commit_on_success_spec.rb +58 -0
- data/spec/unit/nulogy_message_bus_consumer/steps/deduplicate_messages_spec.rb +56 -0
- data/spec/unit/nulogy_message_bus_consumer/steps/log_messages_spec.rb +70 -0
- data/spec/unit/nulogy_message_bus_consumer/steps/monitor_replication_lag/calculator_spec.rb +63 -0
- data/spec/unit/nulogy_message_bus_consumer/steps/stream_messages_spec.rb +35 -0
- data/spec/unit/nulogy_message_bus_consumer_spec.rb +30 -0
- metadata +251 -27
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
RSpec.describe NulogyMessageBusConsumer::Steps::ConnectToMessageBus do
|
|
2
|
+
subject(:pipeline) do
|
|
3
|
+
pipeline = NulogyMessageBusConsumer.recommended_consumer_pipeline(config: config)
|
|
4
|
+
pipeline.insert(tap, after: NulogyMessageBusConsumer::Steps::ConnectToMessageBus)
|
|
5
|
+
pipeline.append(message_handler_spy)
|
|
6
|
+
pipeline
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
let(:topic) { TestTopic.new }
|
|
10
|
+
let(:config) { topic.config }
|
|
11
|
+
let(:tap) { MiddlewareTap.new }
|
|
12
|
+
let(:message_handler_spy) { double }
|
|
13
|
+
|
|
14
|
+
after { topic.close }
|
|
15
|
+
|
|
16
|
+
# TODO: This spec is terribly flakey.
|
|
17
|
+
xit "receives messages" do
|
|
18
|
+
called = false
|
|
19
|
+
expect(message_handler_spy).to receive(:call) do |message:, **_kargs|
|
|
20
|
+
expect(message).to have_attributes(event_data: {data: "Some Payload"})
|
|
21
|
+
called = true
|
|
22
|
+
:success
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
pipeline_thread = start(pipeline, tap)
|
|
26
|
+
|
|
27
|
+
topic.produce_one_message(
|
|
28
|
+
key: "Some Key",
|
|
29
|
+
payload: message_payload(data: "Some Payload")
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
NulogyMessageBusConsumer::KafkaUtils.wait_for { called }
|
|
33
|
+
Thread.kill(pipeline_thread)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def start(pipeline, tap)
|
|
37
|
+
thr = Thread.new { pipeline.invoke }
|
|
38
|
+
wait_for_partition_assignment(tap)
|
|
39
|
+
thr
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def wait_for_partition_assignment(tap)
|
|
43
|
+
NulogyMessageBusConsumer::KafkaUtils.wait_for { tap.arguments[:kafka_consumer] }
|
|
44
|
+
NulogyMessageBusConsumer::KafkaUtils.wait_for_assignment(tap.arguments[:kafka_consumer])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def message_payload(**payload)
|
|
48
|
+
JSON.dump(
|
|
49
|
+
id: SecureRandom.uuid,
|
|
50
|
+
created_at: 1_000,
|
|
51
|
+
event_json: JSON.dump(payload)
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module NulogyMessageBusConsumer
|
|
2
|
+
module Steps
|
|
3
|
+
RSpec.describe SuperviseConsumerLag do
|
|
4
|
+
# RSpec does not like when objects (spies, doubles, etc) are used
|
|
5
|
+
# outside of its scope, e.g.
|
|
6
|
+
# The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported. (RSpec::Mocks::OutsideOfExampleError)
|
|
7
|
+
let(:thread_spy) do
|
|
8
|
+
Class.new do
|
|
9
|
+
attr_reader :killed
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@killed = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def kill
|
|
16
|
+
@killed = true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "kills the main thread after lag does not change" do
|
|
22
|
+
thread = thread_spy.new
|
|
23
|
+
logger = spy
|
|
24
|
+
|
|
25
|
+
consumer = instance_double(
|
|
26
|
+
Rdkafka::Consumer,
|
|
27
|
+
committed: nil,
|
|
28
|
+
lag: {}
|
|
29
|
+
)
|
|
30
|
+
# skip waiting for assignment
|
|
31
|
+
allow(KafkaUtils).to receive(:wait_for_assignment).with(consumer).and_return(nil)
|
|
32
|
+
tracker = instance_double(
|
|
33
|
+
LagTracker,
|
|
34
|
+
failing_checks: 3,
|
|
35
|
+
failing?: true,
|
|
36
|
+
failed: {"topic" => ["partition1", "partition2"]}
|
|
37
|
+
).as_null_object
|
|
38
|
+
|
|
39
|
+
supervisor = described_class.new(
|
|
40
|
+
logger,
|
|
41
|
+
tracker: tracker,
|
|
42
|
+
killable: thread
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
supervisor.call(kafka_consumer: consumer) {}
|
|
46
|
+
|
|
47
|
+
KafkaUtils.wait_for { thread.killed }
|
|
48
|
+
|
|
49
|
+
expect(logger).to have_received(:warn).with(/Assigned partition lag has not changed/)
|
|
50
|
+
expect(thread.killed).to be(true)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
RSpec.describe TestTopic do
|
|
2
|
+
let(:topic) { TestTopic.new }
|
|
3
|
+
|
|
4
|
+
after { topic.close }
|
|
5
|
+
|
|
6
|
+
context "smoke test for specs" do
|
|
7
|
+
it "publishes and receives messages" do
|
|
8
|
+
topic.produce_one_message(
|
|
9
|
+
key: "Some Key",
|
|
10
|
+
payload: "Some Payload"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
message = topic.consume_one_message
|
|
14
|
+
expect(message).to have_attributes(
|
|
15
|
+
key: "Some Key",
|
|
16
|
+
payload: "Some Payload"
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "receives returns nil when no messages are received" do
|
|
21
|
+
consumer = topic.consumer
|
|
22
|
+
message = consumer.poll(1)
|
|
23
|
+
expect(message).to be(nil)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "spec helpers" do
|
|
28
|
+
it "creates topics" do
|
|
29
|
+
create_topic(topic.topic_name)
|
|
30
|
+
expect(list_topics).to include(topic.topic_name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "deletes topics" do
|
|
34
|
+
create_topic(topic.topic_name)
|
|
35
|
+
delete_topic(topic.topic_name)
|
|
36
|
+
expect(list_topics).not_to include(topic.topic_name)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
require "rspec/json_expectations"
|
|
3
|
+
ENV["RAILS_ENV"] ||= "test"
|
|
4
|
+
require File.expand_path("dummy/config/environment", __dir__)
|
|
5
|
+
abort("The Rails environment is running in production mode!") if Rails.env.production?
|
|
6
|
+
require "rspec/rails"
|
|
7
|
+
require "dotenv"
|
|
8
|
+
|
|
9
|
+
env_file = ENV["CI"] == "true" ? ".env.ci" : ".env.local"
|
|
10
|
+
raise "Expected #{env_file}" unless File.exist?(env_file)
|
|
11
|
+
|
|
12
|
+
Dotenv.load(env_file)
|
|
13
|
+
|
|
14
|
+
# Load RSpec helpers.
|
|
15
|
+
ENGINE_ROOT ||= File.expand_path("..", __dir__)
|
|
16
|
+
Dir[File.join(ENGINE_ROOT, "spec/support/**/*.rb")].sort.each { |f| require f }
|
|
17
|
+
|
|
18
|
+
# Load migrations from the dummy app.
|
|
19
|
+
# ActiveRecord::Migrator.migrations_paths = File.join(ENGINE_ROOT, 'spec/dummy/db/migrate')
|
|
20
|
+
begin
|
|
21
|
+
ActiveRecord::Migration.maintain_test_schema!
|
|
22
|
+
rescue ActiveRecord::PendingMigrationError => e
|
|
23
|
+
puts e.to_s.strip
|
|
24
|
+
exit 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
RSpec.configure do |config|
|
|
28
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
|
29
|
+
|
|
30
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
|
31
|
+
config.disable_monkey_patching!
|
|
32
|
+
|
|
33
|
+
config.use_transactional_fixtures = true
|
|
34
|
+
|
|
35
|
+
config.expect_with :rspec do |c|
|
|
36
|
+
c.syntax = :expect
|
|
37
|
+
c.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
config.mock_with :rspec do |mocks|
|
|
41
|
+
mocks.verify_partial_doubles = true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
45
|
+
config.filter_rails_from_backtrace!
|
|
46
|
+
config.use_transactional_fixtures = true
|
|
47
|
+
|
|
48
|
+
config.include(Kafka)
|
|
49
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
|
|
3
|
+
module Kafka
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def kafka_config
|
|
7
|
+
config = {
|
|
8
|
+
"auto.offset.reset": "beginning",
|
|
9
|
+
"bootstrap.servers": test_bootstrap_servers,
|
|
10
|
+
"enable.auto.commit": false,
|
|
11
|
+
"group.id": random_consumer_group
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Rdkafka::Config.new(config)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def random_topic_name
|
|
18
|
+
"test-topic-#{SecureRandom.uuid}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def random_consumer_group
|
|
22
|
+
"ruby-test-consumer-group-#{SecureRandom.uuid}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_bootstrap_servers
|
|
26
|
+
"#{ENV["MBC_KAFKA_HOST"]}:#{ENV["MBC_KAFKA_PORT"]}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def setup_kafka_producer
|
|
30
|
+
kafka_config.producer
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def setup_kafka_consumer(topic_name)
|
|
34
|
+
consumer = kafka_config.consumer
|
|
35
|
+
consumer.subscribe(topic_name)
|
|
36
|
+
NulogyMessageBusConsumer::KafkaUtils.wait_for_assignment(consumer)
|
|
37
|
+
consumer
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create_topic(topic_name)
|
|
41
|
+
run("kaf topic create #{topic_name} --brokers #{test_bootstrap_servers} --replicas 1 --partitions 3")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def delete_topic(topic_name)
|
|
45
|
+
run("kaf topic delete #{topic_name} --brokers #{test_bootstrap_servers}")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def list_topics
|
|
49
|
+
topics = run("kaf topics --brokers #{test_bootstrap_servers}")
|
|
50
|
+
topics.split(" ")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def run(command)
|
|
54
|
+
stdout, stderr, status = Open3.capture3(command)
|
|
55
|
+
raise <<~OUTPUT if status != 0
|
|
56
|
+
Command `#{command}` failed with:
|
|
57
|
+
STDOUT:
|
|
58
|
+
#{stdout}
|
|
59
|
+
|
|
60
|
+
STDERR:
|
|
61
|
+
#{stderr}
|
|
62
|
+
OUTPUT
|
|
63
|
+
|
|
64
|
+
stdout
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_config(topic_name)
|
|
68
|
+
NulogyMessageBusConsumer::Config.new(
|
|
69
|
+
consumer_group_id: random_consumer_group,
|
|
70
|
+
bootstrap_servers: test_bootstrap_servers,
|
|
71
|
+
topic_name: topic_name
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class TestTopic
|
|
2
|
+
attr_reader :topic_name
|
|
3
|
+
|
|
4
|
+
def initialize(topic_name: Kafka.random_topic_name)
|
|
5
|
+
@topic_name = topic_name
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def consumer
|
|
9
|
+
@consumer ||= Kafka.setup_kafka_consumer(topic_name)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def producer
|
|
13
|
+
@producer ||= Kafka.setup_kafka_producer
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def produce_one_message(key: "TEST KEY", payload: '{ "KEY": "TEST PAYLOAD" }', **kwargs)
|
|
17
|
+
if kwargs.key?(:topic)
|
|
18
|
+
raise "Do not specify the topic when producing with a TestTopic. Create a new TestTopic instead."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
id = SecureRandom.uuid
|
|
22
|
+
if kwargs.key?(:event_json)
|
|
23
|
+
kwargs[:payload] = JSON.dump({id: id, event_json: JSON.dump(kwargs.delete(:event_json))})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
producer.produce(
|
|
27
|
+
topic: topic_name,
|
|
28
|
+
key: key,
|
|
29
|
+
payload: payload,
|
|
30
|
+
**kwargs
|
|
31
|
+
).wait
|
|
32
|
+
|
|
33
|
+
id
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def consume_one_message
|
|
37
|
+
consumer.poll(250)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def config
|
|
41
|
+
Kafka.test_config(topic_name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def close
|
|
45
|
+
@producer&.close
|
|
46
|
+
@consumer&.close
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module NulogyMessageBusConsumer
|
|
2
|
+
RSpec.describe Config do
|
|
3
|
+
describe "defaults" do
|
|
4
|
+
it "lag_check_interval_seconds to 20" do
|
|
5
|
+
expect(Config.new.lag_check_interval_seconds).to be(20)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "lag_checks to 6" do
|
|
9
|
+
expect(Config.new.lag_checks).to be(6)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "merges defaults" do
|
|
14
|
+
config = Config.new(lag_checks: 3)
|
|
15
|
+
|
|
16
|
+
expect(config.lag_checks).to be(3)
|
|
17
|
+
expect(config.lag_check_interval_seconds).to be(20)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module NulogyMessageBusConsumer
|
|
2
|
+
RSpec.describe LagTracker do
|
|
3
|
+
it "does not fail when values change" do
|
|
4
|
+
tracker = described_class.new(failing_checks: 1)
|
|
5
|
+
|
|
6
|
+
tracker.update({"one" => {"0" => 1, "1" => 1}})
|
|
7
|
+
tracker.update({"one" => {"0" => 2, "1" => 0}})
|
|
8
|
+
|
|
9
|
+
expect(tracker).not_to be_failing
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "tracks failed when value unchanged enough" do
|
|
13
|
+
tracker = described_class.new(failing_checks: 2)
|
|
14
|
+
|
|
15
|
+
tracker.update({"one" => {"0" => 1, "1" => 1}})
|
|
16
|
+
tracker.update({"one" => {"0" => 1, "1" => 2}})
|
|
17
|
+
tracker.update({"one" => {"0" => 1, "1" => 3}})
|
|
18
|
+
|
|
19
|
+
expect(tracker).to be_failing
|
|
20
|
+
expect(tracker.failed).to eq({
|
|
21
|
+
"one" => ["0"]
|
|
22
|
+
})
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "ignores unchanged 0 values" do
|
|
26
|
+
tracker = described_class.new(failing_checks: 1)
|
|
27
|
+
|
|
28
|
+
tracker.update({"one" => {"0" => 0}})
|
|
29
|
+
tracker.update({"one" => {"0" => 0}})
|
|
30
|
+
tracker.update({"one" => {"0" => 0}})
|
|
31
|
+
|
|
32
|
+
expect(tracker).not_to be_failing
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
RSpec.describe NulogyMessageBusConsumer::Message do
|
|
2
|
+
describe ".from_kafka" do
|
|
3
|
+
let(:message_id) { SecureRandom.uuid }
|
|
4
|
+
let(:subscription_id) { SecureRandom.uuid }
|
|
5
|
+
let(:company_uuid) { SecureRandom.uuid }
|
|
6
|
+
let(:partition_key) { [company_uuid, subscription_id].join(",") }
|
|
7
|
+
|
|
8
|
+
it "builds a message from a kafka message" do
|
|
9
|
+
kafka_message = build_kafka_message(
|
|
10
|
+
payload: JSON.dump(
|
|
11
|
+
id: message_id,
|
|
12
|
+
subscription_id: subscription_id,
|
|
13
|
+
company_uuid: company_uuid,
|
|
14
|
+
event_json: JSON.dump(hello: "world")
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
built = NulogyMessageBusConsumer::Message.from_kafka(kafka_message)
|
|
19
|
+
|
|
20
|
+
expect(built).to have_attributes(
|
|
21
|
+
id: message_id,
|
|
22
|
+
subscription_id: subscription_id,
|
|
23
|
+
company_uuid: company_uuid,
|
|
24
|
+
event_data: {hello: "world"},
|
|
25
|
+
event_data_unparsed: include_json(hello: "world")
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "expects the event_json to be valid json" do
|
|
30
|
+
kafka_message = build_kafka_message(
|
|
31
|
+
payload: JSON.dump(event_json: "not json")
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
built = NulogyMessageBusConsumer::Message.from_kafka(kafka_message)
|
|
35
|
+
|
|
36
|
+
expect(built).to have_attributes(
|
|
37
|
+
event_data: {},
|
|
38
|
+
event_data_unparsed: "not json"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "falls back on some depricated columns" do
|
|
43
|
+
kafka_message = build_kafka_message(
|
|
44
|
+
payload: JSON.dump(
|
|
45
|
+
public_subscription_id: subscription_id,
|
|
46
|
+
tenant_id: company_uuid
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
built = NulogyMessageBusConsumer::Message.from_kafka(kafka_message)
|
|
51
|
+
|
|
52
|
+
expect(built).to have_attributes(
|
|
53
|
+
subscription_id: subscription_id,
|
|
54
|
+
company_uuid: company_uuid
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_kafka_message(overrides = {}) # rubocop:disable Metrics/MethodLength
|
|
60
|
+
attributes = {
|
|
61
|
+
topic: "some_topic",
|
|
62
|
+
partition: 3,
|
|
63
|
+
payload: JSON.dump(
|
|
64
|
+
id: message_id,
|
|
65
|
+
subscription_id: subscription_id,
|
|
66
|
+
company_uuid: company_uuid,
|
|
67
|
+
event_json: JSON.dump(
|
|
68
|
+
hello: "world"
|
|
69
|
+
)
|
|
70
|
+
),
|
|
71
|
+
key: JSON.dump(
|
|
72
|
+
schema: {},
|
|
73
|
+
payload: {
|
|
74
|
+
partition_key: partition_key
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
offset: 13,
|
|
78
|
+
timestamp: Time.zone.now,
|
|
79
|
+
headers: {key: "value"}
|
|
80
|
+
}.merge(overrides)
|
|
81
|
+
|
|
82
|
+
instance_double("Rdkafka::Consumer::Message", attributes)
|
|
83
|
+
end
|
|
84
|
+
end
|