nulogy_message_bus_producer 3.2.1 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +82 -22
  3. data/Rakefile +2 -4
  4. data/db/migrate/20200611150212_create_public_subscriptions_and_events_tables.rb +2 -2
  5. data/lib/nulogy_message_bus_producer.rb +15 -8
  6. data/lib/nulogy_message_bus_producer/base_subscription.rb +1 -1
  7. data/lib/nulogy_message_bus_producer/config.rb +25 -1
  8. data/lib/nulogy_message_bus_producer/configuration/query_parser.rb +71 -0
  9. data/lib/nulogy_message_bus_producer/self_serve_subscription.rb +18 -0
  10. data/lib/nulogy_message_bus_producer/subscriptions/configured_subscription.rb +14 -0
  11. data/lib/nulogy_message_bus_producer/subscriptions/finder.rb +40 -0
  12. data/lib/nulogy_message_bus_producer/subscriptions/postgres_transport.rb +13 -6
  13. data/lib/nulogy_message_bus_producer/subscriptions/query_validator.rb +47 -0
  14. data/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker.rb +0 -5
  15. data/lib/nulogy_message_bus_producer/subscriptions/valid_for_schema_validator.rb +14 -0
  16. data/lib/nulogy_message_bus_producer/version.rb +1 -1
  17. data/spec/dummy/Rakefile +1 -1
  18. data/spec/dummy/app/mailers/application_mailer.rb +2 -2
  19. data/spec/dummy/bin/bundle +2 -2
  20. data/spec/dummy/bin/rails +3 -3
  21. data/spec/dummy/bin/rake +2 -2
  22. data/spec/dummy/bin/setup +14 -16
  23. data/spec/dummy/bin/update +10 -10
  24. data/spec/dummy/bin/yarn +13 -7
  25. data/spec/dummy/config.ru +2 -1
  26. data/spec/dummy/config/application.rb +11 -6
  27. data/spec/dummy/config/boot.rb +2 -4
  28. data/spec/dummy/config/cable.yml +2 -2
  29. data/spec/dummy/config/database.yml +2 -2
  30. data/spec/dummy/config/environment.rb +1 -1
  31. data/spec/dummy/config/environments/development.rb +29 -7
  32. data/spec/dummy/config/environments/production.rb +50 -20
  33. data/spec/dummy/config/environments/test.rb +25 -8
  34. data/spec/dummy/config/initializers/assets.rb +2 -2
  35. data/spec/dummy/config/initializers/backtrace_silencers.rb +4 -3
  36. data/spec/dummy/config/initializers/content_security_policy.rb +30 -0
  37. data/spec/dummy/config/initializers/filter_parameter_logging.rb +3 -1
  38. data/spec/dummy/config/initializers/new_framework_defaults_6_1.rb +67 -0
  39. data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
  40. data/spec/dummy/config/locales/en.yml +1 -1
  41. data/spec/dummy/config/puma.rb +3 -3
  42. data/spec/dummy/config/spring.rb +2 -2
  43. data/spec/dummy/config/storage.yml +34 -0
  44. data/spec/dummy/db/schema.rb +0 -2
  45. data/spec/dummy/log/development.log +317 -0
  46. data/spec/dummy/log/test.log +13164 -0
  47. data/spec/integration/lib/nulogy_message_bus_producer/config_spec.rb +37 -0
  48. data/spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb +6 -7
  49. data/spec/integration/lib/nulogy_message_bus_producer/subscription_spec.rb +3 -57
  50. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/finder_spec.rb +54 -0
  51. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/no_variables_spec.rb +1 -1
  52. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb +100 -54
  53. data/spec/integration/lib/nulogy_message_bus_producer/{subscriber_graphql_schema_validator_spec.rb → subscriptions/query_validator_spec.rb} +3 -3
  54. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb +0 -16
  55. data/spec/integration_spec_helper.rb +0 -6
  56. data/spec/nulogy_message_bus_producer/configuration/query_parser_spec.rb +58 -0
  57. data/spec/nulogy_message_bus_producer/subscriptions/subscription_spec.rb +9 -0
  58. data/spec/spec_helper.rb +26 -1
  59. data/spec/support/kafka.rb +9 -10
  60. data/spec/support/kafka_connect.rb +1 -1
  61. data/spec/support/shared_examples/subscription_validations.rb +77 -0
  62. data/spec/support/skip.rb +9 -0
  63. data/spec/support/sql_helpers.rb +1 -1
  64. data/spec/support/subscription_helpers.rb +22 -4
  65. data/spec/support/test_graphql_schema.rb +6 -2
  66. metadata +131 -79
  67. data/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator.rb +0 -45
  68. data/lib/nulogy_message_bus_producer/subscription.rb +0 -28
@@ -0,0 +1,37 @@
1
+ require "integration_spec_helper"
2
+
3
+ module NulogyMessageBusProducer
4
+ RSpec.describe Config do
5
+ let(:config) { described_class.new }
6
+
7
+ describe "add_subscription" do
8
+ it "adds a subscription" do
9
+ id = SecureRandom.uuid
10
+ group_id = SecureRandom.uuid
11
+ query = <<~QUERY
12
+ subscription {
13
+ testCreated(subscriptionId: "#{id}", subscriptionGroupId: "#{group_id}", topicName: "test-topic") {
14
+ foo {
15
+ id
16
+ }
17
+ }
18
+ }
19
+ QUERY
20
+
21
+ config.add_subscription!(
22
+ schema: "NulogyMessageBusProducer::Specs::TestSchema",
23
+ query: query
24
+ )
25
+
26
+ expect(config.configured_subscriptions).to include(having_attributes(
27
+ id: id,
28
+ subscription_group_id: group_id,
29
+ schema_key: "test",
30
+ event_type: "testCreated",
31
+ topic_name: "test-topic",
32
+ query: query
33
+ ))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -5,8 +5,8 @@ RSpec.describe NulogyMessageBusProducer::RepopulateReplicationSlots do
5
5
  let(:company_uuid) { SecureRandom.uuid }
6
6
  let(:number_of_messages) { 100 }
7
7
 
8
- let(:kafka_bootstrap_servers) { "host.docker.internal:39092" }
9
- let(:kafka_connect) { KafkaConnect.new("http://localhost:8083", "ruby_specs") }
8
+ let(:kafka_bootstrap_servers) { "kafka:39093" }
9
+ let(:kafka_connect) { KafkaConnect.new("http://debezium:8083", "ruby_specs") }
10
10
  let(:replication_slot_name) { "rspec_replication_slot" }
11
11
  let(:topic_name) { "repopulate_replication_slot_tests" }
12
12
 
@@ -14,12 +14,12 @@ RSpec.describe NulogyMessageBusProducer::RepopulateReplicationSlots do
14
14
  cleanup_everything
15
15
  end
16
16
 
17
- it "generates events" do
17
+ it "generates events", skip: flakey_in_ci do
18
18
  Kafka.create_topic(topic_name)
19
19
  consumer = Kafka.setup_kafka_consumer(topic_name)
20
20
 
21
21
  without_transaction do
22
- subscribe_to(event_type: "testCreated", topic_name: topic_name)
22
+ self_serve_subscription(event_type: "testCreated", topic_name: topic_name)
23
23
 
24
24
  number_of_messages.times { |n| create_event(uuid(n)) }
25
25
 
@@ -38,7 +38,7 @@ RSpec.describe NulogyMessageBusProducer::RepopulateReplicationSlots do
38
38
  truncate_db
39
39
  begin
40
40
  Kafka.delete_topic(topic_name)
41
- rescue StandardError
41
+ rescue
42
42
  nil
43
43
  end
44
44
  kafka_connect.delete
@@ -88,9 +88,8 @@ RSpec.describe NulogyMessageBusProducer::RepopulateReplicationSlots do
88
88
 
89
89
  {
90
90
  "bootstrap.servers": kafka_bootstrap_servers,
91
-
92
91
  "database.dbname": db_config["database"],
93
- "database.hostname": db_config["host"] == "localhost" ? "host.docker.internal" : db_config["host"],
92
+ "database.hostname": db_config["host"],
94
93
  "database.password": db_config["password"],
95
94
  "database.port": db_config["port"] || 5432,
96
95
  "database.server.name": "test-environment",
@@ -1,61 +1,7 @@
1
1
  require "integration_spec_helper"
2
2
 
3
- RSpec.describe NulogyMessageBusProducer::Subscription do
4
- context "when validating" do
5
- it "is invalid with a blank query" do
6
- model = build_subscription(query: "")
7
-
8
- model.validate
9
-
10
- expect(model.errors[:query]).to contain_exactly("can't be blank")
11
- end
12
-
13
- it "is invalid with blank schema_key" do
14
- model = build_subscription(schema_key: "")
15
-
16
- model.validate
17
-
18
- expect(model.errors[:schema_key]).to contain_exactly("can't be blank")
19
- end
20
-
21
- it "is invalid with an invalid schema_key" do
22
- model = build_subscription(schema_key: "invalid")
23
-
24
- model.validate
25
-
26
- expect(model.errors[:query]).to contain_exactly(/Could not find a schema for schema_key 'invalid'/)
27
- end
28
-
29
- it "is invalid with an invalid query" do
30
- model = build_subscription(
31
- query: subscription_query(query: "foo { a_field_that_does_not_exist }")
32
- )
33
-
34
- model.validate
35
-
36
- expect(model).not_to be_valid
37
- expect(model.errors[:query]).to contain_exactly(
38
- "Field 'a_field_that_does_not_exist' doesn't exist on type 'testObject' (id: <new_record>)"
39
- )
40
- end
41
-
42
- it "valid with a valid query" do
43
- model = build_subscription(
44
- query: subscription_query(query: "foo { id }")
45
- )
46
-
47
- model.validate
48
-
49
- expect(model.errors).not_to include(:query)
50
- end
51
- end
52
-
53
- def build_subscription(overrides = {})
54
- attrs = {
55
- schema_key: "test",
56
- query: subscription_query
57
- }.merge(overrides)
58
-
59
- NulogyMessageBusProducer::Subscription.new(attrs)
3
+ module NulogyMessageBusProducer
4
+ RSpec.describe SelfServeSubscription do
5
+ include_examples "subscription validations"
60
6
  end
61
7
  end
@@ -0,0 +1,54 @@
1
+ require "integration_spec_helper"
2
+
3
+ module NulogyMessageBusProducer
4
+ module Subscriptions
5
+ RSpec.describe Finder, :subscriptions do
6
+ subject(:resolver) do
7
+ described_class.new(NulogyMessageBusProducer.config)
8
+ end
9
+
10
+ context "when there are only self-serve subscriptions" do
11
+ it "returns matching self-serve subscriptions" do
12
+ create_subscriptions = [self_serve_subscription, self_serve_subscription]
13
+ self_serve_subscription(event_type: "testUpdated")
14
+
15
+ expect(resolver.for_schema_event("test", "testCreated")).to contain_exactly(*create_subscriptions)
16
+ end
17
+ end
18
+
19
+ context "when there are only configured subscriptions" do
20
+ it "returns matching configured subscriptions" do
21
+ create_subscriptions = [configured_subscription, configured_subscription]
22
+ configured_subscription(event_type: "testUpdated")
23
+
24
+ expect(resolver.for_schema_event("test", "testCreated")).to contain_exactly(*create_subscriptions)
25
+ end
26
+ end
27
+
28
+ context "with both present" do
29
+ it "returns both" do
30
+ sub_1 = self_serve_subscription
31
+ sub_2 = configured_subscription
32
+
33
+ expect(resolver.for_schema_event("test", "testCreated")).to contain_exactly(
34
+ sub_1,
35
+ sub_2
36
+ )
37
+ end
38
+
39
+ context "when duplicate subscription ids are present" do
40
+ it "return the configured one" do
41
+ id = SecureRandom.uuid
42
+ self_serve_subscription(subscription_id: id, topic_name: "self-served")
43
+ configured_subscription(subscription_id: id, topic_name: "configured")
44
+
45
+ # Since the IDs are the same, we need to match on attributes to show that are different
46
+ expect(resolver.for_schema_event("test", "testCreated")).to contain_exactly(
47
+ have_attributes(topic_name: "configured")
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -21,7 +21,7 @@ RSpec.describe NulogyMessageBusProducer::Subscriptions::NoVariables do
21
21
  )
22
22
 
23
23
  expect(result).to include_json(
24
- errors: [{ message: include("Subscriptions should not be created with arguments") }]
24
+ errors: [{message: include("Subscriptions should not be created with arguments")}]
25
25
  )
26
26
  end
27
27
 
@@ -1,81 +1,127 @@
1
1
  require "integration_spec_helper"
2
2
 
3
- RSpec.describe NulogyMessageBusProducer::Subscriptions::PostgresTransport do
3
+ RSpec.describe NulogyMessageBusProducer::Subscriptions::PostgresTransport, :subscriptions do
4
4
  context "when subscription is triggered" do
5
5
  let(:company_uuid) { SecureRandom.uuid }
6
-
7
- it "generates an event for a subscription" do
8
- subscription = subscribe_to(
9
- event_type: "testCreated",
10
- topic_name: "some_topic"
11
- )
12
- root_object = {
13
- foo: { id: "some id" },
6
+ let(:root_object) do
7
+ {
8
+ foo: {id: "some id"},
14
9
  company_uuid: company_uuid
15
10
  }
11
+ end
16
12
 
17
- trigger_event("testCreated", root_object)
13
+ context "with self-serve subscriptions" do
14
+ it "generates an event for a subscription" do
15
+ subscription = self_serve_subscription(
16
+ event_type: "testCreated",
17
+ topic_name: "some_topic"
18
+ )
18
19
 
19
- event = NulogyMessageBusProducer::SubscriptionEvent.find_by!(subscription_id: subscription.id)
20
- expect(event).to have_attributes(
21
- partition_key: "#{subscription.subscription_group_id},#{company_uuid}",
22
- topic_name: "some_topic",
23
- event_json: include_json(testCreated: { foo: { id: "some id" } }),
24
- company_uuid: company_uuid
25
- )
26
- end
20
+ trigger_event("testCreated", root_object)
27
21
 
28
- it "allows configuring context" do
29
- NulogyMessageBusProducer.config.context_for_subscription = lambda do |_|
30
- { context_data: "some contextual information" }
22
+ event = NulogyMessageBusProducer::SubscriptionEvent.find_by!(subscription_id: subscription.id)
23
+ expect(event).to have_attributes(
24
+ partition_key: "#{subscription.subscription_group_id},#{company_uuid}",
25
+ topic_name: "some_topic",
26
+ event_json: include_json(testCreated: {foo: {id: "some id"}}),
27
+ company_uuid: company_uuid
28
+ )
31
29
  end
32
- subscription = subscribe_to(
33
- event_type: "testCreated",
34
- query: <<~GRAPHQL
35
- foo {
36
- contextData
37
- }
38
- GRAPHQL
39
- )
40
- root_object = {
41
- foo: {},
42
- company_uuid: company_uuid
43
- }
44
30
 
45
- trigger_event("testCreated", root_object)
31
+ it "allows configuring context" do
32
+ NulogyMessageBusProducer.config.context_for_subscription = lambda do |_|
33
+ {context_data: "some contextual information"}
34
+ end
35
+ subscription = self_serve_subscription(
36
+ event_type: "testCreated",
37
+ query: <<~GRAPHQL
38
+ foo {
39
+ contextData
40
+ }
41
+ GRAPHQL
42
+ )
43
+
44
+ trigger_event("testCreated", root_object)
45
+
46
+ event = NulogyMessageBusProducer::SubscriptionEvent.find_by!(subscription_id: subscription.id)
47
+ expect(event).to have_attributes(
48
+ event_json: include_json(testCreated: {foo: {contextData: "some contextual information"}})
49
+ )
50
+ end
51
+
52
+ it "generates one event per subscription" do
53
+ self_serve_subscription(event_type: "testCreated")
54
+ self_serve_subscription(event_type: "testCreated")
55
+
56
+ expect {
57
+ trigger_event("testCreated", root_object)
58
+ }.to change(NulogyMessageBusProducer::SubscriptionEvent, :count).by(2)
46
59
 
47
- event = NulogyMessageBusProducer::SubscriptionEvent.find_by!(subscription_id: subscription.id)
48
- expect(event).to have_attributes(
49
- event_json: include_json(testCreated: { foo: { contextData: "some contextual information" } })
50
- )
60
+ event_data = NulogyMessageBusProducer::SubscriptionEvent.pluck(:event_json)
61
+ expect(event_data).to all(
62
+ include_json(testCreated: {foo: {id: "some id"}})
63
+ )
64
+ end
51
65
  end
52
66
 
53
- it "generates one event per subscription" do
54
- subscribe_to(event_type: "testCreated")
55
- subscribe_to(event_type: "testCreated")
67
+ context "with configured subscriptions" do
68
+ it "generates an event for a subscription" do
69
+ subscription = configured_subscription(
70
+ event_type: "testCreated",
71
+ topic_name: "some_topic"
72
+ )
56
73
 
57
- expect do
58
- root_object = {
59
- foo: { id: "some id" },
60
- company_uuid: company_uuid
61
- }
62
74
  trigger_event("testCreated", root_object)
63
- end.to change(NulogyMessageBusProducer::SubscriptionEvent, :count).by(2)
64
75
 
65
- event_data = NulogyMessageBusProducer::SubscriptionEvent.pluck(:event_json)
66
- expect(event_data).to all(
67
- include_json(testCreated: { foo: { id: "some id" } })
68
- )
76
+ event = NulogyMessageBusProducer::SubscriptionEvent.find_by!(subscription_id: subscription.id)
77
+ expect(event).to have_attributes(
78
+ partition_key: "#{subscription.subscription_group_id},#{company_uuid}",
79
+ topic_name: "some_topic",
80
+ event_json: include_json(testCreated: {foo: {id: "some id"}}),
81
+ company_uuid: company_uuid
82
+ )
83
+ end
84
+ end
85
+
86
+ context "with both types of subscriptions" do
87
+ it "generates multiple events" do
88
+ self_served = self_serve_subscription(event_type: "testCreated", topic_name: "self_serve_topic")
89
+ configured = configured_subscription(event_type: "testCreated", topic_name: "configured_topic")
90
+
91
+ expect {
92
+ trigger_event("testCreated", root_object)
93
+ }.to change(NulogyMessageBusProducer::SubscriptionEvent, :count).by(2)
94
+ expect(NulogyMessageBusProducer::SubscriptionEvent.find_by!(subscription_id: self_served.id)).to have_attributes(topic_name: "self_serve_topic")
95
+ expect(NulogyMessageBusProducer::SubscriptionEvent.find_by!(subscription_id: configured.id)).to have_attributes(topic_name: "configured_topic")
96
+ end
97
+
98
+ context "when different types of subscriptions have the same id" do
99
+ it "generates events for only the configured subscription" do
100
+ id = SecureRandom.uuid
101
+ self_serve_subscription(subscription_id: id, topic_name: "self_serve_topic")
102
+ configured_subscription(subscription_id: id, topic_name: "configured_topic")
103
+
104
+ root_object = {
105
+ foo: {id: "some id"},
106
+ company_uuid: company_uuid
107
+ }
108
+
109
+ expect {
110
+ trigger_event("testCreated", root_object)
111
+ }.to change(NulogyMessageBusProducer::SubscriptionEvent, :count).by(1)
112
+ expect(NulogyMessageBusProducer::SubscriptionEvent.find_by!(subscription_id: id)).to have_attributes(topic_name: "configured_topic")
113
+ end
114
+ end
69
115
  end
70
116
  end
71
117
 
72
118
  context "when the class is not registered" do
73
119
  it "raises" do
74
- expect do
120
+ expect {
75
121
  Class.new(GraphQL::Schema) do
76
122
  use(NulogyMessageBusProducer::Subscriptions::PostgresTransport)
77
123
  end
78
- end.to raise_error(KeyError, /The schema registry did not contain an entry/)
124
+ }.to raise_error(KeyError, /The schema registry did not contain an entry/)
79
125
  end
80
126
  end
81
127
 
@@ -116,7 +162,7 @@ RSpec.describe NulogyMessageBusProducer::Subscriptions::PostgresTransport do
116
162
  def trigger_erroneous_subscription
117
163
  event_type = "testCreated"
118
164
 
119
- subscription = subscribe_to(event_type: event_type)
165
+ subscription = self_serve_subscription(event_type: event_type)
120
166
  simulate_invalid_query(subscription)
121
167
 
122
168
  trigger_event(event_type, uuid: SecureRandom.uuid, company_uuid: SecureRandom.uuid)
@@ -1,12 +1,12 @@
1
1
  require "integration_spec_helper"
2
2
 
3
- RSpec.describe NulogyMessageBusProducer::SubscriberGraphqlSchemaValidator do
3
+ RSpec.describe NulogyMessageBusProducer::Subscriptions::QueryValidator do
4
4
  subject(:validator) { described_class.new }
5
5
 
6
6
  describe "#validate" do
7
7
  context "when a valid query is present" do
8
8
  it "return true" do
9
- subscribe_to(query: <<~GRAPHQL)
9
+ self_serve_subscription(query: <<~GRAPHQL)
10
10
  foo {
11
11
  id
12
12
  }
@@ -18,7 +18,7 @@ RSpec.describe NulogyMessageBusProducer::SubscriberGraphqlSchemaValidator do
18
18
 
19
19
  context "when an invalid query is present" do
20
20
  let(:subscription_with_error) do
21
- subscription = subscribe_to(query: <<~GRAPHQL)
21
+ subscription = self_serve_subscription(query: <<~GRAPHQL)
22
22
  foo {
23
23
  id
24
24
  }
@@ -17,22 +17,6 @@ RSpec.describe NulogyMessageBusProducer::Subscriptions::RiskySubscriptionBlocker
17
17
  )
18
18
  end
19
19
 
20
- it "blocks subscriptions which would expand lists" do
21
- query = <<~GRAPHQL
22
- fooList {
23
- id
24
- }
25
- GRAPHQL
26
-
27
- result = attempt_subscription(query)
28
-
29
- expect(result).to include_json(
30
- errors: [{
31
- message: "Lists may not be queried:\nfooList"
32
- }]
33
- )
34
- end
35
-
36
20
  def attempt_subscription(query)
37
21
  execute_graphql(<<~GRAPHQL, NulogyMessageBusProducer::Specs::TestSchema)
38
22
  subscription {