kafka_command 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 (159) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +179 -0
  3. data/.env +1 -0
  4. data/.env.test +1 -0
  5. data/.gitignore +41 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +12 -0
  8. data/.ruby-version +1 -0
  9. data/Gemfile +17 -0
  10. data/Gemfile.lock +194 -0
  11. data/LICENSE +21 -0
  12. data/README.md +138 -0
  13. data/Rakefile +34 -0
  14. data/app/assets/config/manifest.js +3 -0
  15. data/app/assets/images/.keep +0 -0
  16. data/app/assets/images/kafka_command/cluster_view.png +0 -0
  17. data/app/assets/images/kafka_command/kafka.png +0 -0
  18. data/app/assets/images/kafka_command/topic_view.png +0 -0
  19. data/app/assets/javascripts/kafka_command/application.js +14 -0
  20. data/app/assets/stylesheets/kafka_command/application.css +27 -0
  21. data/app/assets/stylesheets/kafka_command/clusters.css +8 -0
  22. data/app/assets/stylesheets/kafka_command/topics.css +3 -0
  23. data/app/channels/application_cable/channel.rb +6 -0
  24. data/app/channels/application_cable/connection.rb +6 -0
  25. data/app/controllers/kafka_command/application_controller.rb +96 -0
  26. data/app/controllers/kafka_command/brokers_controller.rb +26 -0
  27. data/app/controllers/kafka_command/clusters_controller.rb +46 -0
  28. data/app/controllers/kafka_command/consumer_groups_controller.rb +44 -0
  29. data/app/controllers/kafka_command/topics_controller.rb +187 -0
  30. data/app/helpers/kafka_command/application_helper.rb +29 -0
  31. data/app/helpers/kafka_command/consumer_group_helper.rb +13 -0
  32. data/app/jobs/application_job.rb +6 -0
  33. data/app/mailers/application_mailer.rb +8 -0
  34. data/app/models/kafka_command/broker.rb +47 -0
  35. data/app/models/kafka_command/client.rb +102 -0
  36. data/app/models/kafka_command/cluster.rb +172 -0
  37. data/app/models/kafka_command/consumer_group.rb +142 -0
  38. data/app/models/kafka_command/consumer_group_partition.rb +23 -0
  39. data/app/models/kafka_command/group_member.rb +18 -0
  40. data/app/models/kafka_command/partition.rb +36 -0
  41. data/app/models/kafka_command/topic.rb +153 -0
  42. data/app/views/kafka_command/brokers/index.html.erb +38 -0
  43. data/app/views/kafka_command/clusters/_tabs.html.erb +9 -0
  44. data/app/views/kafka_command/clusters/index.html.erb +54 -0
  45. data/app/views/kafka_command/clusters/new.html.erb +115 -0
  46. data/app/views/kafka_command/configuration_error.html.erb +1 -0
  47. data/app/views/kafka_command/consumer_groups/index.html.erb +32 -0
  48. data/app/views/kafka_command/consumer_groups/show.html.erb +115 -0
  49. data/app/views/kafka_command/shared/_alert.html.erb +13 -0
  50. data/app/views/kafka_command/shared/_search_bar.html.erb +31 -0
  51. data/app/views/kafka_command/shared/_title.html.erb +6 -0
  52. data/app/views/kafka_command/topics/_form_fields.html.erb +49 -0
  53. data/app/views/kafka_command/topics/edit.html.erb +17 -0
  54. data/app/views/kafka_command/topics/index.html.erb +46 -0
  55. data/app/views/kafka_command/topics/new.html.erb +36 -0
  56. data/app/views/kafka_command/topics/show.html.erb +126 -0
  57. data/app/views/layouts/kafka_command/application.html.erb +50 -0
  58. data/bin/rails +16 -0
  59. data/config/initializers/kafka.rb +13 -0
  60. data/config/initializers/kafka_command.rb +11 -0
  61. data/config/routes.rb +11 -0
  62. data/docker-compose.yml +18 -0
  63. data/kafka_command.gemspec +27 -0
  64. data/lib/assets/.keep +0 -0
  65. data/lib/core_extensions/kafka/broker/attr_readers.rb +11 -0
  66. data/lib/core_extensions/kafka/broker_pool/attr_readers.rb +11 -0
  67. data/lib/core_extensions/kafka/client/attr_readers.rb +11 -0
  68. data/lib/core_extensions/kafka/cluster/attr_readers.rb +11 -0
  69. data/lib/core_extensions/kafka/protocol/metadata_response/partition_metadata/attr_readers.rb +15 -0
  70. data/lib/kafka_command/configuration.rb +150 -0
  71. data/lib/kafka_command/engine.rb +11 -0
  72. data/lib/kafka_command/errors.rb +6 -0
  73. data/lib/kafka_command/version.rb +5 -0
  74. data/lib/kafka_command.rb +13 -0
  75. data/lib/tasks/.keep +0 -0
  76. data/spec/dummy/Rakefile +6 -0
  77. data/spec/dummy/app/assets/config/manifest.js +4 -0
  78. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  79. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  80. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  81. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  82. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  83. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  84. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  85. data/spec/dummy/app/jobs/application_job.rb +2 -0
  86. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  87. data/spec/dummy/app/models/application_record.rb +3 -0
  88. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  89. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  90. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  91. data/spec/dummy/bin/bundle +3 -0
  92. data/spec/dummy/bin/rails +4 -0
  93. data/spec/dummy/bin/rake +4 -0
  94. data/spec/dummy/bin/setup +36 -0
  95. data/spec/dummy/bin/update +31 -0
  96. data/spec/dummy/bin/yarn +11 -0
  97. data/spec/dummy/config/application.rb +19 -0
  98. data/spec/dummy/config/boot.rb +5 -0
  99. data/spec/dummy/config/cable.yml +10 -0
  100. data/spec/dummy/config/database.yml +25 -0
  101. data/spec/dummy/config/environment.rb +5 -0
  102. data/spec/dummy/config/environments/development.rb +61 -0
  103. data/spec/dummy/config/environments/production.rb +94 -0
  104. data/spec/dummy/config/environments/test.rb +46 -0
  105. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  106. data/spec/dummy/config/initializers/assets.rb +14 -0
  107. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  108. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  109. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  110. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  111. data/spec/dummy/config/initializers/inflections.rb +16 -0
  112. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  113. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  114. data/spec/dummy/config/kafka_command.yml +18 -0
  115. data/spec/dummy/config/locales/en.yml +33 -0
  116. data/spec/dummy/config/puma.rb +34 -0
  117. data/spec/dummy/config/routes.rb +3 -0
  118. data/spec/dummy/config/spring.rb +6 -0
  119. data/spec/dummy/config/ssl/test_ca_cert +1 -0
  120. data/spec/dummy/config/ssl/test_client_cert +1 -0
  121. data/spec/dummy/config/ssl/test_client_cert_key +1 -0
  122. data/spec/dummy/config/storage.yml +34 -0
  123. data/spec/dummy/config.ru +5 -0
  124. data/spec/dummy/db/schema.rb +42 -0
  125. data/spec/dummy/db/test.sqlite3 +0 -0
  126. data/spec/dummy/log/development.log +0 -0
  127. data/spec/dummy/log/hey.log +0 -0
  128. data/spec/dummy/log/test.log +2227 -0
  129. data/spec/dummy/package.json +5 -0
  130. data/spec/dummy/public/404.html +67 -0
  131. data/spec/dummy/public/422.html +67 -0
  132. data/spec/dummy/public/500.html +66 -0
  133. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  134. data/spec/dummy/public/apple-touch-icon.png +0 -0
  135. data/spec/dummy/public/favicon.ico +0 -0
  136. data/spec/examples.txt +165 -0
  137. data/spec/fast_helper.rb +20 -0
  138. data/spec/fixtures/files/kafka_command_sasl.yml +10 -0
  139. data/spec/fixtures/files/kafka_command_ssl.yml +10 -0
  140. data/spec/fixtures/files/kafka_command_ssl_file_paths.yml +11 -0
  141. data/spec/fixtures/files/kafka_command_staging.yml +8 -0
  142. data/spec/lib/kafka_command/configuration_spec.rb +311 -0
  143. data/spec/models/kafka_command/broker_spec.rb +83 -0
  144. data/spec/models/kafka_command/client_spec.rb +306 -0
  145. data/spec/models/kafka_command/cluster_spec.rb +163 -0
  146. data/spec/models/kafka_command/consumer_group_partition_spec.rb +43 -0
  147. data/spec/models/kafka_command/consumer_group_spec.rb +236 -0
  148. data/spec/models/kafka_command/partition_spec.rb +95 -0
  149. data/spec/models/kafka_command/topic_spec.rb +311 -0
  150. data/spec/rails_helper.rb +63 -0
  151. data/spec/requests/json/brokers_spec.rb +50 -0
  152. data/spec/requests/json/clusters_spec.rb +58 -0
  153. data/spec/requests/json/consumer_groups_spec.rb +139 -0
  154. data/spec/requests/json/topics_spec.rb +274 -0
  155. data/spec/spec_helper.rb +109 -0
  156. data/spec/support/factory_bot.rb +5 -0
  157. data/spec/support/json_helper.rb +13 -0
  158. data/spec/support/kafka_helper.rb +93 -0
  159. metadata +326 -0
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe KafkaCommand::Cluster do
4
+ let(:cluster) { described_class.all.first }
5
+ let(:topic_name) { SecureRandom.hex(12) }
6
+
7
+ describe '#new' do
8
+ let(:brokers) { ENV['SEED_BROKERS'].split(',') }
9
+
10
+ context 'plaintext' do
11
+ it 'creates a kafka client with brokers and client id' do
12
+ expect(KafkaCommand::Client).to receive(:new).with(
13
+ hash_including(
14
+ brokers: brokers,
15
+ client_id: 'test_cluster'
16
+ )
17
+ ).at_least(:once)
18
+
19
+ cluster
20
+
21
+ expect(cluster.ssl?).to eq(false)
22
+ expect(cluster.sasl?).to eq(false)
23
+ end
24
+ end
25
+
26
+ context 'sasl config' do
27
+ before do
28
+ allow(KafkaCommand).to receive(:config).and_return(
29
+ KafkaCommand::Configuration.new(KafkaCommand::Configuration.parse_yaml('spec/fixtures/files/kafka_command_sasl.yml'))
30
+ )
31
+ end
32
+
33
+ it 'creates a kafka client with brokers, client_id, and sasl params' do
34
+ expect(KafkaCommand::Client).to receive(:new).with(
35
+ brokers: brokers,
36
+ client_id: 'sasl_test_cluster',
37
+ sasl_scram_username: 'test',
38
+ sasl_scram_password: 'test',
39
+ sasl_scram_mechanism: 'sha256',
40
+ ssl_ca_cert: 'test_ca_cert'
41
+ )
42
+
43
+ cluster
44
+
45
+ expect(cluster.sasl?).to eq(true)
46
+ end
47
+ end
48
+
49
+ context 'ssl' do
50
+ before do
51
+ allow(KafkaCommand).to receive(:config).and_return(
52
+ KafkaCommand::Configuration.new(KafkaCommand::Configuration.parse_yaml('spec/fixtures/files/kafka_command_ssl.yml'))
53
+ )
54
+ end
55
+
56
+ it 'creates a kafka client with brokers, client_id, and ssl params' do
57
+ expect(KafkaCommand::Client).to receive(:new).with(
58
+ brokers: brokers,
59
+ client_id: 'ssl_test_cluster',
60
+ ssl_client_cert: 'test_client_cert',
61
+ ssl_client_cert_key: 'test_client_cert_key',
62
+ ssl_ca_cert: 'test_ca_cert'
63
+ )
64
+
65
+ cluster
66
+
67
+ expect(cluster.ssl?).to eq(true)
68
+ expect(cluster.sasl?).to eq(false)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#create_topic' do
74
+ let(:kwargs) do
75
+ { replication_factor: 1, num_partitions: 1 }
76
+ end
77
+
78
+ it 'calls KafkaCommand::Client#create_topic' do
79
+ expect_any_instance_of(KafkaCommand::Client).to receive(:create_topic).with(topic_name, **kwargs)
80
+ cluster.create_topic(topic_name, **kwargs)
81
+ end
82
+
83
+ it 'creates a topic' do
84
+ topic_count = cluster.topics.count
85
+ topic = cluster.create_topic(topic_name, **kwargs)
86
+ expect(topic).to be_an_instance_of(KafkaCommand::Topic)
87
+ cluster.client.refresh_topics!
88
+ expect(cluster.topics.count).to eq(topic_count + 1)
89
+ end
90
+ end
91
+
92
+ describe '#to_s' do
93
+ it 'returns the name' do
94
+ expect(cluster.to_s).to eq(cluster.name)
95
+ end
96
+ end
97
+
98
+ describe '.all' do
99
+ it "returns a list of #{described_class}" do
100
+ described_class.all.each do |c|
101
+ expect(c).to be_an_instance_of(described_class)
102
+ end
103
+ end
104
+ end
105
+
106
+ describe '.find' do
107
+ context 'cluster exists' do
108
+ it 'finds the cluster' do
109
+ expect(described_class.find(cluster.name)).to eq(cluster)
110
+ end
111
+ end
112
+
113
+ context 'cluster does not exist' do
114
+ it 'returns nil' do
115
+ expect(described_class.find('doesnotexists')).to be_nil
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'forwarding' do
121
+ describe '#topics' do
122
+ before { create_topic(topic_name) }
123
+
124
+ it 'fowards the call to the KafkaCommand::Client' do
125
+ expect(cluster.client).to receive(:topics).once
126
+ cluster.topics
127
+ end
128
+
129
+ it 'returns a list of topics' do
130
+ expect(cluster.topics.first).to be_an_instance_of(KafkaCommand::Topic)
131
+ expect(cluster.topics.map(&:name)).to include(topic_name)
132
+ end
133
+ end
134
+
135
+ describe '#brokers' do
136
+ it 'fowards the call to the KafkaCommand::Client' do
137
+ expect(cluster.client).to receive(:brokers).once
138
+ cluster.brokers
139
+ end
140
+
141
+ it 'returns a list of brokers' do
142
+ expect(cluster.brokers.first).to be_an_instance_of(KafkaCommand::Broker)
143
+ end
144
+ end
145
+
146
+ describe '#groups' do
147
+ before { create_topic(topic_name) }
148
+ let(:group_id) { SecureRandom.hex(12) }
149
+
150
+ it 'fowards to the call to the KafkaCommand::Client' do
151
+ expect(cluster.client).to receive(:groups).once
152
+ cluster.groups
153
+ end
154
+
155
+ it 'returns a list of groups' do
156
+ run_consumer_group(topic_name, group_id) do
157
+ expect(cluster.groups.first).to be_an_instance_of(KafkaCommand::ConsumerGroup)
158
+ expect(cluster.groups.map(&:group_id)).to include(group_id)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe KafkaCommand::ConsumerGroupPartition do
4
+ let(:group_id) { "group-#{SecureRandom.hex(12)}" }
5
+ let(:topic_name) { "test-#{SecureRandom.hex(12)}" }
6
+ let(:lag) { 1 }
7
+ let(:offset) { 200 }
8
+ let(:partition_id) { 1 }
9
+
10
+ let(:consumer_group_partition_) do
11
+ described_class.new(
12
+ group_id: group_id,
13
+ lag: lag,
14
+ offset: offset,
15
+ topic_name: topic_name,
16
+ partition_id: partition_id
17
+ )
18
+ end
19
+
20
+ describe '#new' do
21
+ it 'creates a new Kafka::ConsumerGroupPartition with attributes' do
22
+ expect(consumer_group_partition_.lag).to eq(lag)
23
+ expect(consumer_group_partition_.group_id).to eq(group_id)
24
+ expect(consumer_group_partition_.topic_name).to eq(topic_name)
25
+ expect(consumer_group_partition_.offset).to eq(offset)
26
+ expect(consumer_group_partition_.partition_id).to eq(partition_id)
27
+ end
28
+ end
29
+
30
+ describe '#as_json' do
31
+ let(:expected_json) do
32
+ {
33
+ lag: lag,
34
+ offset: offset,
35
+ partition_id: partition_id
36
+ }
37
+ end
38
+
39
+ it 'returns the expected payload' do
40
+ expect(consumer_group_partition_.as_json).to eq(expected_json)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe KafkaCommand::ConsumerGroup do
4
+ let(:group_id) { "test-group-#{SecureRandom.hex(24)}" }
5
+ let(:topic_name) { "test-topic-#{SecureRandom.hex(24)}" }
6
+ let(:num_partitions) { 5 }
7
+ let(:group) do
8
+ KafkaCommand::Cluster.all.first.groups.find { |g| g.group_id == group_id }
9
+ end
10
+
11
+ before { create_topic(topic_name, num_partitions: num_partitions) }
12
+
13
+ describe '#new' do
14
+ describe 'running' do
15
+ it 'initializes a stable Kafka::ConsumerGroup with group_id, members, and state' do
16
+ run_consumer_group(topic_name, group_id) do
17
+ expect(group.group_id).to eq(group_id)
18
+ expect(group.members).to_not be_empty
19
+ expect(group.members.sample).to be_an_instance_of(KafkaCommand::GroupMember)
20
+ expect(group.state).to eq('Stable')
21
+ end
22
+ end
23
+ end
24
+
25
+ describe 'dormant' do
26
+ before { run_consumer_group(topic_name, group_id) }
27
+
28
+ it 'initializes an empty Kafka::ConsumerGroup with group_id, members, and state' do
29
+ expect(group.group_id).to eq(group_id)
30
+ expect(group.members).to be_empty
31
+ expect(group.state).to eq('Empty')
32
+ end
33
+ end
34
+ end
35
+
36
+ describe '#refresh!' do
37
+ it 'refreshes the group metadata' do
38
+ run_consumer_group(topic_name, group_id) do
39
+ expect(group.state).to eq('Stable')
40
+ expect(group.members).to_not be_empty
41
+ end
42
+
43
+ group.refresh!
44
+ expect(group.members).to be_empty
45
+ expect(group.state).to eq('Empty')
46
+ end
47
+ end
48
+
49
+ describe '#stable?' do
50
+ describe 'running' do
51
+ it 'returns true' do
52
+ run_consumer_group(topic_name, group_id) do
53
+ expect(group.stable?).to eq(true)
54
+ end
55
+ end
56
+ end
57
+
58
+ describe 'dormant' do
59
+ before { run_consumer_group(topic_name, group_id) }
60
+
61
+ it 'returns false' do
62
+ expect(group.stable?).to eq(false)
63
+ end
64
+ end
65
+ end
66
+
67
+ describe '#stable?' do
68
+ describe 'running' do
69
+ it 'returns true' do
70
+ run_consumer_group(topic_name, group_id) do
71
+ expect(group.stable?).to eq(true)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe 'dormant' do
77
+ before { run_consumer_group(topic_name, group_id) }
78
+
79
+ it 'returns false' do
80
+ expect(group.stable?).to eq(false)
81
+ end
82
+ end
83
+ end
84
+
85
+ describe '#empty?' do
86
+ describe 'running' do
87
+ it 'returns false' do
88
+ run_consumer_group(topic_name, group_id) do
89
+ expect(group.empty?).to eq(false)
90
+ end
91
+ end
92
+ end
93
+
94
+ describe 'dormant' do
95
+ before { run_consumer_group(topic_name, group_id) }
96
+
97
+ it 'returns true' do
98
+ expect(group.empty?).to eq(true)
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '#partitions_for' do
104
+ let(:num_partitions) { 3 }
105
+ let(:partitions) { group.partitions_for(topic_name) }
106
+ let(:total_lag) { partitions.map(&:lag).compact.reduce(:+) }
107
+
108
+ it 'returns ConsumerGroupPartitions' do
109
+ run_consumer_group(topic_name, group_id) do
110
+ expect(partitions.count).to eq(num_partitions)
111
+ expect(partitions.sample).to be_an_instance_of(KafkaCommand::ConsumerGroupPartition)
112
+ expect(partitions.sample.group_id).to eq(group_id)
113
+ expect(partitions.sample.topic_name).to eq(topic_name)
114
+ end
115
+ end
116
+
117
+ context 'some partitions no offsets' do
118
+ before { run_consumer_group(topic_name, group_id) }
119
+
120
+ it 'has "nil" lag' do
121
+ expect(partitions.map(&:lag)).to include(nil)
122
+ end
123
+ end
124
+
125
+ context 'lag' do
126
+ let(:partition_0) do
127
+ partitions.find { |p| p.partition_id == 0 }
128
+ end
129
+
130
+ let(:partition_1) do
131
+ partitions.find { |p| p.partition_id == 1 }
132
+ end
133
+
134
+ let(:partition_2) do
135
+ partitions.find { |p| p.partition_id == 2 }
136
+ end
137
+
138
+ before do
139
+ deliver_message('test', topic: topic_name, partition: 0)
140
+ deliver_message('test1', topic: topic_name, partition: 1)
141
+ deliver_message('test2', topic: topic_name, partition: 2)
142
+ run_consumer_group(topic_name, group_id, num_messages_to_consume: 3)
143
+ end
144
+
145
+ describe 'no lag' do
146
+ it 'has no lag' do
147
+ expect(partition_0.lag).to eq(0)
148
+ expect(partition_1.lag).to eq(0)
149
+ expect(partition_2.lag).to eq(0)
150
+ end
151
+ end
152
+
153
+ describe 'some lag' do
154
+ before do
155
+ 2.times { deliver_message('test', topic: topic_name, partition: 0) }
156
+ 3.times { deliver_message('test1', topic: topic_name, partition: 1) }
157
+ end
158
+
159
+ it 'has lag' do
160
+ expect(total_lag).to eq(5)
161
+ expect(partition_0.lag).to eq(2)
162
+ expect(partition_1.lag).to eq(3)
163
+ expect(partition_2.lag).to eq(0)
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ describe '#consumed_topics' do
170
+ context 'running' do
171
+ it 'returns a list of topics' do
172
+ run_consumer_group(topic_name, group_id) do
173
+ expect(group.consumed_topics.map(&:name)).to include(topic_name)
174
+ end
175
+ end
176
+ end
177
+
178
+ context 'dormant' do
179
+ before { run_consumer_group(topic_name, group_id) }
180
+
181
+ it 'returns an empty list' do
182
+ expect(group.consumed_topics).to be_empty
183
+ end
184
+ end
185
+ end
186
+
187
+ describe '#as_json' do
188
+ let(:num_partitions) { 3 }
189
+ let(:partitions) { group.partitions_for(topic_name) }
190
+
191
+
192
+ before do
193
+ deliver_message('test', topic: topic_name, partition: 0)
194
+ deliver_message('test1', topic: topic_name, partition: 1)
195
+ deliver_message('test2', topic: topic_name, partition: 2)
196
+ run_consumer_group(topic_name, group_id, num_messages_to_consume: 3)
197
+ 2.times { deliver_message('test', topic: topic_name, partition: 0) }
198
+ 3.times { deliver_message('test1', topic: topic_name, partition: 1) }
199
+ end
200
+
201
+ context 'running' do
202
+ let(:expected_json) do
203
+ {
204
+ group_id: group_id,
205
+ topics: [
206
+ {
207
+ name: topic_name,
208
+ partitions: partitions.map(&:as_json)
209
+ }
210
+ ],
211
+ state: 'Stable'
212
+ }
213
+ end
214
+
215
+ it 'returns the expected payload' do
216
+ run_consumer_group(topic_name, group_id) do
217
+ expect(group.as_json).to eq(expected_json)
218
+ end
219
+ end
220
+ end
221
+
222
+ context 'dormant' do
223
+ let(:expected_json) do
224
+ {
225
+ group_id: group_id,
226
+ topics: [],
227
+ state: 'Empty'
228
+ }
229
+ end
230
+
231
+ it 'returns the expected payload' do
232
+ expect(group.as_json).to eq(expected_json)
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe KafkaCommand::Partition do
4
+ let(:topic_name) { "test-#{SecureRandom.hex(12)}" }
5
+ subject do
6
+ KafkaCommand::Cluster.all.first.topics.find { |t| t.name == topic_name }.partitions.sample
7
+ end
8
+
9
+ before { create_topic(topic_name) }
10
+
11
+ describe '#new' do
12
+ it 'contains a reference to a topic wrapper' do
13
+ expect(subject.topic).to be_an_instance_of(KafkaCommand::Topic)
14
+ end
15
+ end
16
+
17
+ describe '#highwater_mark_offset' do
18
+ let(:num_messages) { 2 }
19
+
20
+ before do
21
+ num_messages.times { deliver_message('test', topic: topic_name) }
22
+ end
23
+
24
+ it 'retrieves the latest offset' do
25
+ expect(subject.highwater_mark_offset).to eq(num_messages)
26
+ end
27
+ end
28
+
29
+ describe '#as_json' do
30
+ let(:expected_json) do
31
+ {
32
+ isr: subject.isr,
33
+ leader: subject.leader,
34
+ id: subject.partition_id,
35
+ highwater_mark_offset: subject.highwater_mark_offset
36
+ }.to_json
37
+ end
38
+
39
+ it 'equals the expected json' do
40
+ expect(subject.as_json.to_json).to eq(expected_json)
41
+ end
42
+ end
43
+
44
+ context 'forwarding' do
45
+ let(:partition_metadata) do
46
+ subject.instance_variable_get(:@partition_metadata)
47
+ end
48
+
49
+ describe '#isr' do
50
+ it 'forwards isr to the partition metadata' do
51
+ expect(partition_metadata).to receive(:isr)
52
+ subject.isr
53
+ end
54
+
55
+ it 'returns the isr' do
56
+ expect(subject.isr).to be_an_instance_of(Array)
57
+ expect(subject.isr.first).to be_an_instance_of(Integer)
58
+ end
59
+ end
60
+
61
+ describe '#leader' do
62
+ it 'forwards leader to the partition metadata' do
63
+ expect(partition_metadata).to receive(:leader)
64
+ subject.leader
65
+ end
66
+
67
+ it 'returns the leader broker' do
68
+ expect(subject.leader).to be_an_instance_of(Integer)
69
+ end
70
+ end
71
+
72
+ describe '#partition_id' do
73
+ it 'forwards leader to the partition metadata' do
74
+ expect(partition_metadata).to receive(:leader)
75
+ subject.leader
76
+ end
77
+
78
+ it 'returns the partition id' do
79
+ expect(subject.partition_id).to be > -1
80
+ end
81
+ end
82
+
83
+ describe '#replicas' do
84
+ it 'forwards replicas to the partition metadata' do
85
+ expect(partition_metadata).to receive(:replicas)
86
+ subject.replicas
87
+ end
88
+
89
+ it 'returns the replica node ids' do
90
+ expect(subject.replicas).to be_an_instance_of(Array)
91
+ expect(subject.replicas.first).to be_an_instance_of(Integer)
92
+ end
93
+ end
94
+ end
95
+ end