nulogy_message_bus_producer 1.0.4 → 3.2.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +183 -15
  3. data/Rakefile +6 -2
  4. data/db/migrate/20201005150212_rename_tenant_id_and_public.rb +6 -0
  5. data/lib/nulogy_message_bus_producer.rb +61 -25
  6. data/lib/nulogy_message_bus_producer/{base_public_subscription.rb → base_subscription.rb} +1 -1
  7. data/lib/nulogy_message_bus_producer/config.rb +72 -0
  8. data/lib/nulogy_message_bus_producer/repopulate_replication_slots.rb +23 -0
  9. data/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator.rb +1 -1
  10. data/lib/nulogy_message_bus_producer/{public_subscription.rb → subscription.rb} +4 -3
  11. data/lib/nulogy_message_bus_producer/{public_subscription_event.rb → subscription_event.rb} +1 -1
  12. data/lib/nulogy_message_bus_producer/subscriptions/postgres_transport.rb +85 -0
  13. data/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker.rb +58 -0
  14. data/lib/nulogy_message_bus_producer/version.rb +1 -1
  15. data/lib/tasks/engine/message_bus_producer.rake +11 -0
  16. data/spec/dummy/config/database.yml +1 -1
  17. data/spec/dummy/db/migrate/20201005164116_create_active_storage_tables.active_storage.rb +5 -0
  18. data/spec/dummy/db/schema.rb +3 -5
  19. data/spec/dummy/log/development.log +2217 -31
  20. data/spec/dummy/log/test.log +27556 -16
  21. data/spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb +133 -0
  22. data/spec/integration/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator_spec.rb +49 -0
  23. data/spec/integration/lib/nulogy_message_bus_producer/subscription_spec.rb +63 -0
  24. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb +137 -0
  25. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb +51 -0
  26. data/spec/integration_spec_helper.rb +6 -0
  27. data/spec/spec_helper.rb +0 -25
  28. data/spec/support/kafka.rb +98 -0
  29. data/spec/support/kafka_connect.rb +31 -0
  30. data/spec/support/spec_utils.rb +15 -0
  31. data/spec/support/sql_helpers.rb +47 -0
  32. data/spec/support/subscription_helpers.rb +52 -0
  33. data/spec/support/test_graphql_schema.rb +47 -0
  34. metadata +88 -39
  35. data/lib/nulogy_message_bus_producer/postgres_public_subscriptions.rb +0 -102
  36. data/spec/integration/lib/graphql_api/postgres_public_subscriptions_spec.rb +0 -16
  37. data/spec/integration/lib/graphql_api/validators/subscriber_graphql_schema_validator_spec.rb +0 -76
  38. data/spec/unit/lib/graphql_api/models/public_subscription_spec.rb +0 -56
  39. data/spec/unit_spec_helper.rb +0 -6
@@ -1,28 +1,3 @@
1
- # Some test GraphQL objects for testing
2
- module NulogyMessageBusProducer
3
- module Specs
4
- class TestObject < GraphQL::Schema::Object
5
- graphql_name "testObject"
6
-
7
- field :id, type: NulogyGraphqlApi::Types::UUID, null: false
8
- end
9
-
10
- class TestQuery < GraphQL::Schema::Object
11
- graphql_name "testQuery"
12
-
13
- field :foo, TestObject, null: false do
14
- argument :id, type: NulogyGraphqlApi::Types::UUID, required: false
15
- end
16
- end
17
-
18
- class TestSchema < GraphQL::Schema
19
- use NulogyMessageBusProducer::PostgresPublicSubscriptions
20
-
21
- query TestQuery
22
- end
23
- end
24
- end
25
-
26
1
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
27
2
  RSpec.configure do |config|
28
3
  config.expect_with(:rspec) do |expectations|
@@ -0,0 +1,98 @@
1
+ require "open3"
2
+ require "rdkafka"
3
+
4
+ module Kafka
5
+ module_function
6
+
7
+ def kafka_config
8
+ config = {
9
+ "auto.offset.reset": "beginning",
10
+ "bootstrap.servers": test_bootstrap_servers,
11
+ "enable.auto.commit": false,
12
+ "group.id": random_consumer_group
13
+ }
14
+
15
+ Rdkafka::Config.new(config)
16
+ end
17
+
18
+ def random_topic_name
19
+ "test-topic-#{SecureRandom.uuid}"
20
+ end
21
+
22
+ def random_consumer_group
23
+ "ruby-test-consumer-group-#{SecureRandom.uuid}"
24
+ end
25
+
26
+ def test_bootstrap_servers
27
+ "localhost:9092"
28
+ end
29
+
30
+ def setup_kafka_producer
31
+ kafka_config.producer
32
+ end
33
+
34
+ def setup_kafka_consumer(topic_name)
35
+ consumer = kafka_config.consumer
36
+ consumer.subscribe(topic_name)
37
+ wait_for_assignment(consumer)
38
+ consumer
39
+ end
40
+
41
+ def get_all_messages(consumer, timeout: 500)
42
+ messages = []
43
+
44
+ loop do
45
+ message = consumer.poll(timeout)
46
+
47
+ if message
48
+ messages << message
49
+ else
50
+ return messages
51
+ end
52
+ end
53
+ end
54
+
55
+ def wait_for_messages(consumer)
56
+ messages = []
57
+ SpecUtils.wait_for(attempts: 10) do
58
+ messages = Kafka.get_all_messages(consumer)
59
+ messages.present?
60
+ end
61
+ messages
62
+ end
63
+
64
+ def create_topic(topic_name)
65
+ run("docker-compose exec -T kafka kafka-topics --zookeeper zookeeper:2181 --create --replication-factor 1 --partitions 3 --if-not-exists --topic #{topic_name}")
66
+ end
67
+
68
+ def delete_topic(topic_name)
69
+ run("docker-compose exec -T kafka kafka-topics --zookeeper zookeeper:2181 --delete --topic #{topic_name}")
70
+ end
71
+
72
+ def list_topics
73
+ topics = run("docker-compose exec -T kafka kafka-topics --zookeeper zookeeper:2181 --list")
74
+ topics.split(" ")
75
+ end
76
+
77
+ def run(command)
78
+ stdout, stderr, status = Open3.capture3(command)
79
+ raise <<~OUTPUT if status != 0
80
+ Command `#{command}` failed with:
81
+ STDOUT:
82
+ #{stdout}
83
+
84
+ STDERR:
85
+ #{stderr}
86
+ OUTPUT
87
+
88
+ stdout
89
+ end
90
+
91
+ def wait_for_assignment(consumer)
92
+ SpecUtils.wait_for { !consumer.assignment.empty? }
93
+ end
94
+
95
+ def wait_for_unassignment(consumer)
96
+ SpecUtils.wait_for { consumer.assignment.empty? }
97
+ end
98
+ end
@@ -0,0 +1,31 @@
1
+ class KafkaConnect
2
+ def initialize(host, connector_name)
3
+ uri = URI(host)
4
+ @http = Net::HTTP.new(uri.host, uri.port)
5
+ @connector_name = connector_name
6
+ end
7
+
8
+ def configure(config)
9
+ request = Net::HTTP::Put.new("/connectors/#{@connector_name}/config")
10
+ request.body = config.to_json
11
+ request.content_type = "application/json"
12
+ @http.request(request)
13
+ end
14
+
15
+ def delete
16
+ request = Net::HTTP::Delete.new("/connectors/#{@connector_name}")
17
+ @http.request(request)
18
+ end
19
+
20
+ def info
21
+ request = Net::HTTP::Get.new("/connectors/#{@connector_name}")
22
+ response = @http.request(request)
23
+ JSON.parse(response.body, symbolize_names: true)
24
+ end
25
+
26
+ def status
27
+ request = Net::HTTP::Get.new("/connectors/#{@connector_name}/status")
28
+ response = @http.request(request)
29
+ JSON.parse(response.body, symbolize_names: true)
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module SpecUtils
2
+ module_function
3
+
4
+ def wait_for(attempts: 100, interval: 0.1)
5
+ attempts.times do
6
+ return if yield
7
+ sleep interval
8
+ end
9
+ raise "Waited for #{attempts} times but it never resolved"
10
+ end
11
+
12
+ def uuid(entity_number)
13
+ sprintf("00000000-0000-0000-0000-%12.12d", entity_number)
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ module SqlHelpers
2
+ def without_transaction
3
+ begin
4
+ ActiveRecord::Base.connection.rollback_transaction
5
+ yield
6
+ ensure
7
+ truncate_db
8
+ end
9
+ end
10
+
11
+ def truncate_db
12
+ # Hit any tables that were used via active record
13
+ ActiveRecord::Base.connection.tables.each do |table|
14
+ ActiveRecord::Base.connection.execute("TRUNCATE #{table}")
15
+ end
16
+
17
+ # Just incase these models weren't loaded, do them explicitly
18
+ ActiveRecord::Base.connection.execute("TRUNCATE #{NulogyMessageBusProducer::SubscriptionEvent.table_name}")
19
+ ActiveRecord::Base.connection.execute("TRUNCATE #{NulogyMessageBusProducer::Subscription.table_name}")
20
+ end
21
+
22
+ def get_replication_slots
23
+ results = ActiveRecord::Base.connection.exec_query(<<~SQL)
24
+ SELECT slot_name FROM pg_replication_slots
25
+ SQL
26
+
27
+ results.to_a.map { |result| result["slot_name"] }
28
+ end
29
+
30
+ def drop_replication_slot(slot_name)
31
+ ActiveRecord::Base.connection.execute(<<~SQL)
32
+ SELECT pg_drop_replication_slot('#{slot_name}')
33
+ SQL
34
+ end
35
+
36
+ def wait_for_replication_slot(slot_name)
37
+ wait_for do
38
+ get_replication_slots.any? { |replication_slot| replication_slot == slot_name }
39
+ end
40
+ end
41
+
42
+ def wait_for_replication_slot_cleanup(slot_name)
43
+ wait_for do
44
+ get_replication_slots.none? { |replication_slot| replication_slot == slot_name }
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ module SubscriptionHelpers
2
+ def subscribe_to(
3
+ schema: NulogyMessageBusProducer::Specs::TestSchema,
4
+ subscription_id: SecureRandom.uuid,
5
+ **query_args
6
+ )
7
+ gql = subscription_query(subscription_id: subscription_id, **query_args)
8
+
9
+ expect do
10
+ gql_response = execute_graphql(gql, schema)
11
+ expect(gql_response).to eq(data: {})
12
+ end.to change(NulogyMessageBusProducer::Subscription, :count).by(1)
13
+
14
+ NulogyMessageBusProducer::Subscription.find(subscription_id)
15
+ end
16
+
17
+ def subscription_query(
18
+ event_type: "testCreated",
19
+ query: "foo { id }",
20
+ subscription_id: SecureRandom.uuid,
21
+ subscription_group_id: SecureRandom.uuid,
22
+ topic_name: "some_topic"
23
+ )
24
+ <<~GRAPHQL
25
+ subscription {
26
+ #{event_type} (
27
+ subscriptionId: "#{subscription_id}",
28
+ subscriptionGroupId: "#{subscription_group_id}",
29
+ topicName: "#{topic_name}"
30
+ ) {
31
+ #{query}
32
+ }
33
+ }
34
+ GRAPHQL
35
+ end
36
+
37
+ def execute_graphql(query, schema, variables: {}, context: {})
38
+ response = schema.execute(
39
+ query,
40
+ variables: variables,
41
+ context: context,
42
+ operation_name: nil
43
+ )
44
+
45
+ response.to_h.deep_symbolize_keys
46
+ end
47
+
48
+ def trigger_event(event_type, root_object)
49
+ schema = NulogyMessageBusProducer::Specs::TestSchema
50
+ NulogyMessageBusProducer.trigger_event(schema, event_type, root_object)
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ # Some test GraphQL objects for testing
2
+ NulogyMessageBusProducer.config.register_schema(
3
+ schema: "NulogyMessageBusProducer::Specs::TestSchema",
4
+ key: "test"
5
+ )
6
+
7
+ module NulogyMessageBusProducer
8
+ module Specs
9
+ class TestObject < GraphQL::Schema::Object
10
+ graphql_name "testObject"
11
+
12
+ field :id, type: String, null: false
13
+ field :field_with_arguments, type: String, null: false do
14
+ argument :first, String, required: false
15
+ end
16
+ field :context_data, type: String, null: false
17
+
18
+ def context_data
19
+ context[:context_data]
20
+ end
21
+ end
22
+
23
+ class TestQuery < GraphQL::Schema::Object
24
+ field :dummy_value, String, null: false
25
+ end
26
+
27
+ class TestCreated < NulogyMessageBusProducer::BaseSubscription
28
+ field :foo, TestObject, null: false
29
+ field :foo_list, [TestObject], null: false
30
+ end
31
+
32
+ class TestSubscription < GraphQL::Schema::Object
33
+ field :test_created, subscription: TestCreated
34
+ end
35
+
36
+ class TestSchema < GraphQL::Schema
37
+ use GraphQL::Execution::Interpreter
38
+ use GraphQL::Analysis::AST
39
+ use NulogyMessageBusProducer::Subscriptions::PostgresTransport
40
+
41
+ query TestQuery
42
+ subscription TestSubscription
43
+
44
+ query_analyzer NulogyMessageBusProducer::Subscriptions::RiskySubscriptionBlocker
45
+ end
46
+ end
47
+ end
metadata CHANGED
@@ -1,57 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nulogy_message_bus_producer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nulogy
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-13 00:00:00.000000000 Z
11
+ date: 2020-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.2.4.2
19
+ version: '5'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 5.2.4.2
26
+ version: '5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: graphql
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '='
32
- - !ruby/object:Gem::Version
33
- version: 1.9.12
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '='
39
- - !ruby/object:Gem::Version
40
- version: 1.9.12
41
- - !ruby/object:Gem::Dependency
42
- name: strong_migrations
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - '='
31
+ - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: 0.5.0
33
+ version: 1.11.4
48
34
  type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
- - - '='
38
+ - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: 0.5.0
40
+ version: 1.11.4
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: pg
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +94,48 @@ dependencies:
108
94
  - - '='
109
95
  - !ruby/object:Gem::Version
110
96
  version: 13.0.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake-release
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.2.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.2.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: rdkafka
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec-json_expectations
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 2.2.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 2.2.0
111
139
  - !ruby/object:Gem::Dependency
112
140
  name: rspec-rails
113
141
  requirement: !ruby/object:Gem::Requirement
@@ -188,15 +216,20 @@ files:
188
216
  - README.md
189
217
  - Rakefile
190
218
  - db/migrate/20200611150212_create_public_subscriptions_and_events_tables.rb
219
+ - db/migrate/20201005150212_rename_tenant_id_and_public.rb
191
220
  - lib/nulogy_message_bus_producer.rb
192
221
  - lib/nulogy_message_bus_producer/application_record.rb
193
- - lib/nulogy_message_bus_producer/base_public_subscription.rb
222
+ - lib/nulogy_message_bus_producer/base_subscription.rb
223
+ - lib/nulogy_message_bus_producer/config.rb
194
224
  - lib/nulogy_message_bus_producer/engine.rb
195
- - lib/nulogy_message_bus_producer/postgres_public_subscriptions.rb
196
- - lib/nulogy_message_bus_producer/public_subscription.rb
197
- - lib/nulogy_message_bus_producer/public_subscription_event.rb
225
+ - lib/nulogy_message_bus_producer/repopulate_replication_slots.rb
198
226
  - lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator.rb
227
+ - lib/nulogy_message_bus_producer/subscription.rb
228
+ - lib/nulogy_message_bus_producer/subscription_event.rb
229
+ - lib/nulogy_message_bus_producer/subscriptions/postgres_transport.rb
230
+ - lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker.rb
199
231
  - lib/nulogy_message_bus_producer/version.rb
232
+ - lib/tasks/engine/message_bus_producer.rake
200
233
  - spec/dummy/Rakefile
201
234
  - spec/dummy/app/assets/config/manifest.js
202
235
  - spec/dummy/app/assets/javascripts/application.js
@@ -241,6 +274,7 @@ files:
241
274
  - spec/dummy/config/routes.rb
242
275
  - spec/dummy/config/secrets.yml
243
276
  - spec/dummy/config/spring.rb
277
+ - spec/dummy/db/migrate/20201005164116_create_active_storage_tables.active_storage.rb
244
278
  - spec/dummy/db/schema.rb
245
279
  - spec/dummy/log/development.log
246
280
  - spec/dummy/log/test.log
@@ -251,16 +285,23 @@ files:
251
285
  - spec/dummy/public/apple-touch-icon-precomposed.png
252
286
  - spec/dummy/public/apple-touch-icon.png
253
287
  - spec/dummy/public/favicon.ico
254
- - spec/integration/lib/graphql_api/postgres_public_subscriptions_spec.rb
255
- - spec/integration/lib/graphql_api/validators/subscriber_graphql_schema_validator_spec.rb
288
+ - spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb
289
+ - spec/integration/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator_spec.rb
290
+ - spec/integration/lib/nulogy_message_bus_producer/subscription_spec.rb
291
+ - spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb
292
+ - spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb
256
293
  - spec/integration_spec_helper.rb
257
294
  - spec/spec_helper.rb
258
- - spec/unit/lib/graphql_api/models/public_subscription_spec.rb
259
- - spec/unit_spec_helper.rb
295
+ - spec/support/kafka.rb
296
+ - spec/support/kafka_connect.rb
297
+ - spec/support/spec_utils.rb
298
+ - spec/support/sql_helpers.rb
299
+ - spec/support/subscription_helpers.rb
300
+ - spec/support/test_graphql_schema.rb
260
301
  homepage: https://github.com/nulogy/message-bus/tree/master/gems/nulogy_message_bus_producer
261
302
  licenses: []
262
303
  metadata: {}
263
- post_install_message:
304
+ post_install_message:
264
305
  rdoc_options: []
265
306
  require_paths:
266
307
  - lib
@@ -276,12 +317,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
276
317
  version: '0'
277
318
  requirements: []
278
319
  rubygems_version: 3.0.3
279
- signing_key:
320
+ signing_key:
280
321
  specification_version: 4
281
322
  summary: Nulogy's code for producing to the Message Bus
282
323
  test_files:
283
324
  - spec/spec_helper.rb
284
- - spec/unit/lib/graphql_api/models/public_subscription_spec.rb
285
325
  - spec/dummy/app/mailers/application_mailer.rb
286
326
  - spec/dummy/app/models/application_record.rb
287
327
  - spec/dummy/app/jobs/application_job.rb
@@ -334,9 +374,18 @@ test_files:
334
374
  - spec/dummy/public/apple-touch-icon-precomposed.png
335
375
  - spec/dummy/package.json
336
376
  - spec/dummy/db/schema.rb
377
+ - spec/dummy/db/migrate/20201005164116_create_active_storage_tables.active_storage.rb
337
378
  - spec/dummy/log/test.log
338
379
  - spec/dummy/log/development.log
339
380
  - spec/integration_spec_helper.rb
340
- - spec/integration/lib/graphql_api/postgres_public_subscriptions_spec.rb
341
- - spec/integration/lib/graphql_api/validators/subscriber_graphql_schema_validator_spec.rb
342
- - spec/unit_spec_helper.rb
381
+ - spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb
382
+ - spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb
383
+ - spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb
384
+ - spec/integration/lib/nulogy_message_bus_producer/subscription_spec.rb
385
+ - spec/integration/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator_spec.rb
386
+ - spec/support/kafka_connect.rb
387
+ - spec/support/subscription_helpers.rb
388
+ - spec/support/kafka.rb
389
+ - spec/support/spec_utils.rb
390
+ - spec/support/sql_helpers.rb
391
+ - spec/support/test_graphql_schema.rb