nulogy_message_bus_producer 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -17
  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 +8 -4
  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/subscription.rb +3 -1
  10. data/lib/nulogy_message_bus_producer/subscriptions/finder.rb +40 -0
  11. data/lib/nulogy_message_bus_producer/subscriptions/postgres_transport.rb +9 -2
  12. data/lib/nulogy_message_bus_producer/subscriptions/query_validator.rb +47 -0
  13. data/lib/nulogy_message_bus_producer/version.rb +1 -1
  14. data/spec/dummy/Rakefile +1 -1
  15. data/spec/dummy/app/mailers/application_mailer.rb +2 -2
  16. data/spec/dummy/bin/bundle +2 -2
  17. data/spec/dummy/bin/rails +3 -3
  18. data/spec/dummy/bin/rake +2 -2
  19. data/spec/dummy/bin/setup +10 -11
  20. data/spec/dummy/bin/update +10 -10
  21. data/spec/dummy/bin/yarn +6 -8
  22. data/spec/dummy/config.ru +1 -1
  23. data/spec/dummy/config/boot.rb +3 -3
  24. data/spec/dummy/config/environment.rb +1 -1
  25. data/spec/dummy/config/environments/development.rb +2 -2
  26. data/spec/dummy/config/environments/production.rb +5 -5
  27. data/spec/dummy/config/environments/test.rb +2 -2
  28. data/spec/dummy/config/initializers/assets.rb +2 -2
  29. data/spec/dummy/config/puma.rb +3 -3
  30. data/spec/dummy/config/spring.rb +2 -2
  31. data/spec/dummy/db/schema.rb +0 -2
  32. data/spec/dummy/log/development.log +2333 -0
  33. data/spec/dummy/log/test.log +44636 -0
  34. data/spec/integration/lib/nulogy_message_bus_producer/config_spec.rb +37 -0
  35. data/spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb +2 -2
  36. data/spec/integration/lib/nulogy_message_bus_producer/subscription_spec.rb +20 -2
  37. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/finder_spec.rb +54 -0
  38. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/no_variables_spec.rb +1 -1
  39. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb +100 -54
  40. data/spec/integration/lib/nulogy_message_bus_producer/{subscriber_graphql_schema_validator_spec.rb → subscriptions/query_validator_spec.rb} +3 -3
  41. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb +0 -16
  42. data/spec/nulogy_message_bus_producer/configuration/query_parser_spec.rb +58 -0
  43. data/spec/spec_helper.rb +21 -1
  44. data/spec/support/kafka_connect.rb +1 -1
  45. data/spec/support/subscription_helpers.rb +21 -3
  46. data/spec/support/test_graphql_schema.rb +6 -0
  47. metadata +23 -25
  48. data/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator.rb +0 -45
@@ -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
@@ -19,7 +19,7 @@ RSpec.describe NulogyMessageBusProducer::RepopulateReplicationSlots do
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
@@ -2,6 +2,22 @@ require "integration_spec_helper"
2
2
 
3
3
  RSpec.describe NulogyMessageBusProducer::Subscription do
4
4
  context "when validating" do
5
+ it "is invalid without an id" do
6
+ model = build_subscription(id: "")
7
+
8
+ model.validate
9
+
10
+ expect(model.errors[:id]).to contain_exactly("can't be blank")
11
+ end
12
+
13
+ it "is invalid without a subscription_group_id" do
14
+ model = build_subscription(subscription_group_id: "")
15
+
16
+ model.validate
17
+
18
+ expect(model.errors[:subscription_group_id]).to contain_exactly("can't be blank")
19
+ end
20
+
5
21
  it "is invalid with a blank query" do
6
22
  model = build_subscription(query: "")
7
23
 
@@ -35,7 +51,7 @@ RSpec.describe NulogyMessageBusProducer::Subscription do
35
51
 
36
52
  expect(model).not_to be_valid
37
53
  expect(model.errors[:query]).to contain_exactly(
38
- "Field 'a_field_that_does_not_exist' doesn't exist on type 'testObject' (id: <new_record>)"
54
+ match(/Field 'a_field_that_does_not_exist' doesn't exist on type 'testObject'/)
39
55
  )
40
56
  end
41
57
 
@@ -52,8 +68,10 @@ RSpec.describe NulogyMessageBusProducer::Subscription do
52
68
 
53
69
  def build_subscription(overrides = {})
54
70
  attrs = {
71
+ id: SecureRandom.uuid,
72
+ subscription_group_id: SecureRandom.uuid,
55
73
  schema_key: "test",
56
- query: subscription_query
74
+ query: subscription_query,
57
75
  }.merge(overrides)
58
76
 
59
77
  NulogyMessageBusProducer::Subscription.new(attrs)
@@ -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 {
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+
3
+ module NulogyMessageBusProducer
4
+ module Configuration
5
+ RSpec.describe QueryParser do
6
+ it "parses queries" do
7
+ query = <<~QUERY
8
+ subscription {
9
+ testCreated(subscriptionId: "abc", subscriptionGroupId: "123", topicName: "test-topic") {
10
+ foo {
11
+ id
12
+ }
13
+ }
14
+ }
15
+ QUERY
16
+
17
+ qp = QueryParser.new(query)
18
+
19
+ expect(qp.subscription_id).to eq("abc")
20
+ expect(qp.subscription_group_id).to eq("123")
21
+ expect(qp.event_type).to eq("testCreated")
22
+ expect(qp.topic).to eq("test-topic")
23
+ end
24
+
25
+ context "when query is invalid" do
26
+ it "raises errors for event type" do
27
+ query = <<~QUERY
28
+ testCreated(subscriptionId: "abc", subscriptionGroupId: "123", topicName: "test-topic") {
29
+ foo {
30
+ id
31
+ }
32
+ }
33
+ QUERY
34
+
35
+ qp = QueryParser.new(query)
36
+
37
+ expect { qp.event_type }.to raise_error QueryParser::ParseError, /Error extracting event type/
38
+ end
39
+
40
+ it "raises errors for topic" do
41
+ query = <<~QUERY
42
+ subscription {
43
+ testCreated(subscriptionId: "abc", subscriptionGroupId: "123") {
44
+ foo {
45
+ id
46
+ }
47
+ }
48
+ }
49
+ QUERY
50
+
51
+ qp = QueryParser.new(query)
52
+
53
+ expect { qp.topic }.to raise_error QueryParser::ParseError, /Error extracting topic/
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end