active_event_store 1.0.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8791927b35712c61094dcb6cc915e2b27912129e675c84230cd6f5271585902e
4
- data.tar.gz: fe58f1151444dc521caa4666adf06dd97d00bc37882843b1252d776683a98dcf
3
+ metadata.gz: e19560b568e8765b86d3925070dd1414b0de0545107e1dc1c7df4e3402148690
4
+ data.tar.gz: 87816ce897dc26614f8834993373bb271345c25934142f055547ad0f5023fe22
5
5
  SHA512:
6
- metadata.gz: 95f22443b58343d34b93b0400b53c63bf5190ff5f0c8e56c3c96c2e9ebf3d8137bee3136035f7f12d17f8c4488f8bdebef3a4e971eb67dfd17dbafdb48b857ba
7
- data.tar.gz: c947302a3db94490b5a781f3fb74bacc0b45d437240599766dd33138147b8807119604105b55034482861254463f4a49919dba2689d52eb16599459c2fe15b7a
6
+ metadata.gz: acf6017590c69d26066617c86ab63cd658c2307e8da89c53ec0afe18ebb4b384430bde9f632806f4e92a26293ed37cffa1c7abaa55ca2cd5f312d70d98ba873a
7
+ data.tar.gz: 181e900564fb3640a5484045ebbe6170b3871e05ea10a8e443bcadf7a530bf58bc227f4101d998a92ff96b82b569d1d24adcc1f97d3195c57fa2f0023c7db7db
data/CHANGELOG.md CHANGED
@@ -2,9 +2,14 @@
2
2
 
3
3
  ## master
4
4
 
5
- ## 1.0.0 (2021-01-14)
5
+ ## 1.0.1 (2021-09-16)
6
+
7
+ - Add minitest assertions: `assert_event_published`, `refute_event_published`, `assert_async_event_subscriber_enqueued` ([@chriscz][])
8
+
9
+ ## 1.0.0 (2021-09-14)
6
10
 
7
11
  - Ruby 2.6+, Rails 6+ and RailsEventStore 2.1+ is required.
12
+
8
13
  ## 0.2.1 (2020-09-30)
9
14
 
10
15
  - Fix Active Support load hook name. ([@palkan][])
@@ -20,3 +25,4 @@ Now `ActiveSupport.on_load(:active_event_store) { ... }` works.
20
25
  - Open source Active Event Store. ([@palkan][])
21
26
 
22
27
  [@palkan]: https://github.com/palkan
28
+ [@chriscz]: https://github.com/chriscz
data/README.md CHANGED
@@ -150,20 +150,32 @@ We suggest putting subscribers to the `app/subscribers` folder using the followi
150
150
 
151
151
  You can test subscribers as normal Ruby objects.
152
152
 
153
- **NOTE:** Currently, we provide additional matchers only for RSpec. PRs with Minitest support are welcomed!
153
+ **NOTE** To test using minitest include the `ActiveEventStore::TestHelpers` module in your tests.
154
154
 
155
155
  To test that a given subscriber exists, you can use the `have_enqueued_async_subscriber_for` matcher:
156
156
 
157
157
  ```ruby
158
- # for asynchronous subscriptions
158
+ # for asynchronous subscriptions (rspec)
159
159
  it "is subscribed to some event" do
160
160
  event = MyEvent.new(some: "data")
161
161
  expect { ActiveEventStore.publish event }
162
162
  .to have_enqueued_async_subscriber_for(MySubscriberService)
163
163
  .with(event)
164
164
  end
165
+
166
+ # for asynchronous subscriptions (minitest)
167
+ def test_is_subscribed_to_some_event
168
+ event = MyEvent.new(some: "data")
169
+
170
+ assert_async_event_subscriber_enqueued(MySubscriberService, event: event) do
171
+ ActiveEventStore.publish event
172
+ end
173
+ end
165
174
  ```
166
175
 
176
+ **NOTE** Async event subscribers are queued only after the current transaction has committed so when using `assert_enqued_async_subcriber` in rails
177
+ make sure to have `self.use_transactional_fixtures = false` at the top of your test class.
178
+
167
179
  **NOTE:** You must have `rspec-rails` gem in your bundle to use `have_enqueued_async_subscriber_for` matcher.
168
180
 
169
181
  For synchronous subscribers using `have_received` is enough:
@@ -183,10 +195,14 @@ end
183
195
  To test event publishing, use `have_published_event` matcher:
184
196
 
185
197
  ```ruby
198
+ # rspec
186
199
  expect { subject }.to have_published_event(ProfileCreated).with(user_id: user.id)
200
+
201
+ # minitest
202
+ assert_event_published(ProfileCreated, with: {user_id: user.id}) { subject }
187
203
  ```
188
204
 
189
- **NOTE:** `have_published_event` only supports block expectations.
205
+ **NOTE:** `have_published_event` and `assert_event_published` only supports block expectations.
190
206
 
191
207
  **NOTE 2** `with` modifier works like `have_attributes` matcher (not `contain_exactly`); you can only specify serializable attributes in `with` (i.e. sync attributes are not supported, 'cause they are not persistent).
192
208
 
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEventStore
4
+ module TestHelper
5
+ class EventPublishedMatcher
6
+ attr_reader :attributes,
7
+ :matching_events
8
+
9
+ def initialize(expected_event_class, store: nil, with: nil, exactly: nil, at_least: nil, at_most: nil, refute: false)
10
+ @event_class = expected_event_class
11
+ @store = store || ActiveEventStore.event_store
12
+ @attributes = with
13
+ @refute = refute
14
+
15
+ count_expectations = {
16
+ exactly: exactly,
17
+ at_most: at_most,
18
+ at_least: at_least
19
+ }.reject { |_, v| v.nil? }
20
+
21
+ if count_expectations.length > 1
22
+ raise ArgumentError("Only one of :exactly, :at_least or :at_most can be specified")
23
+ elsif count_expectations.length == 0
24
+ @count_expectation_kind = :at_least
25
+ @expected_count = 1
26
+ else
27
+ @count_expectation_kind = count_expectations.keys.first
28
+ @expected_count = count_expectations.values.first
29
+ end
30
+ end
31
+
32
+ def with_published_events(&block)
33
+ original_count = @store.read.count
34
+ block.call
35
+ in_block_events(original_count, @store.read.count)
36
+ end
37
+
38
+ def matches?(block)
39
+ raise ArgumentError, "#{assertion_name} only support block assertions" if block.nil?
40
+
41
+ events = with_published_events do
42
+ block.call
43
+ end
44
+
45
+ @matching_events, @unmatching_events = partition_events(events)
46
+
47
+ mismatch_message = count_mismatch_message(@matching_events.size)
48
+
49
+ if mismatch_message
50
+ expectations = [
51
+ "Expected #{mismatch_message} #{@event_class.identifier}"
52
+ ]
53
+
54
+ expectations << if refute?
55
+ report_events = @matching_events
56
+ "not to have been published"
57
+ else
58
+ report_events = @unmatching_events
59
+ "to have been published"
60
+ end
61
+
62
+ expectations << "with attributes #{attributes.inspect}" unless attributes.nil?
63
+
64
+ expectations << expectations.pop + ", but"
65
+
66
+ expectations << if report_events.any?
67
+ report_events.inject("published the following events instead:") do |msg, event|
68
+ msg + "\n #{event.inspect}"
69
+ end
70
+ else
71
+ "hasn't published anything"
72
+ end
73
+
74
+ return expectations.join(" ")
75
+ end
76
+
77
+ nil
78
+ end
79
+
80
+ private
81
+
82
+ def refute?
83
+ @refute
84
+ end
85
+
86
+ def assertion_name
87
+ if refute?
88
+ "refute_event_published"
89
+ else
90
+ "assert_event_published"
91
+ end
92
+ end
93
+
94
+ def negate_on_refute(cond)
95
+ if refute?
96
+ !cond
97
+ else
98
+ cond
99
+ end
100
+ end
101
+
102
+ def in_block_events(before_block_count, after_block_count)
103
+ count_difference = after_block_count - before_block_count
104
+ if count_difference.positive?
105
+ @store.read.backward.limit(count_difference).to_a
106
+ else
107
+ []
108
+ end
109
+ end
110
+
111
+ # Partitions events into matching and unmatching
112
+ def partition_events(events)
113
+ events.partition { |e| self.class.event_matches?(@event_class, @attributes, e) }
114
+ end
115
+
116
+ def count_mismatch_message(actual_count)
117
+ case @count_expectation_kind
118
+ when :exactly
119
+ if negate_on_refute(actual_count != @expected_count)
120
+ "exactly #{@expected_count}"
121
+ end
122
+ when :at_most
123
+ if negate_on_refute(actual_count > @expected_count)
124
+ "at most #{@expected_count}"
125
+ end
126
+ when :at_least
127
+ if negate_on_refute(actual_count < @expected_count)
128
+ "at least #{@expected_count}"
129
+ end
130
+ else
131
+ raise ArgumentError, "Unrecognized expectation kind: #{@count_expectation_kind}"
132
+ end
133
+ end
134
+
135
+ class << self
136
+ def event_matches?(event_class, attributes, event)
137
+ event_type_matches?(event_class, event) && event_data_matches?(attributes, event)
138
+ end
139
+
140
+ def event_type_matches?(event_class, event)
141
+ event_class.identifier == event.event_type
142
+ end
143
+
144
+ def event_data_matches?(attributes, event)
145
+ (attributes.nil? || attributes.all? { |k, v| v == event.public_send(k) })
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_event_store/test_helper/event_published_matcher"
4
+
5
+ module ActiveEventStore
6
+ module TestHelper
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ include ActiveJob::TestHelper
11
+ end
12
+
13
+ # Asserts that the given event was published `exactly`, `at_least` or `at_most` number of times
14
+ # to a specific `store` `with` a particular hash of attributes.
15
+ def assert_event_published(expected_event, store: nil, with: nil, exactly: nil, at_least: nil, at_most: nil, &block)
16
+ matcher = EventPublishedMatcher.new(
17
+ expected_event,
18
+ store: store,
19
+ with: with,
20
+ exactly: exactly,
21
+ at_least: at_least,
22
+ at_most: at_most
23
+ )
24
+
25
+ if (msg = matcher.matches?(block))
26
+ fail(msg)
27
+ end
28
+
29
+ matcher.matching_events
30
+ end
31
+
32
+ # Asserts that the given event was *not* published `exactly`, `at_least` or `at_most` number of times
33
+ # to a specific `store` `with` a particular hash of attributes.
34
+ def refute_event_published(expected_event, store: nil, with: nil, exactly: nil, at_least: nil, at_most: nil, &block)
35
+ matcher = EventPublishedMatcher.new(
36
+ expected_event,
37
+ store: store,
38
+ with: with,
39
+ exactly: exactly,
40
+ at_least: at_least,
41
+ at_most: at_most,
42
+ refute: true
43
+ )
44
+
45
+ if (msg = matcher.matches?(block))
46
+ fail(msg)
47
+ end
48
+ end
49
+
50
+ def assert_async_event_subscriber_enqueued(subscriber_class, event: nil, queue: "events_subscribers", &block)
51
+ subscriber_job = ActiveEventStore::SubscriberJob.for(subscriber_class)
52
+ if subscriber_job.nil?
53
+ fail("No such async subscriber: #{subscriber_class.name}")
54
+ end
55
+
56
+ expected_event = event
57
+ event_matcher = ->(actual_event) { EventPublishedMatcher.event_matches?(expected_event, expected_event.data, actual_event) }
58
+
59
+ expected_args = if expected_event
60
+ event_matcher
61
+ end
62
+
63
+ assert_enqueued_with(job: subscriber_job, queue: queue, args: expected_args) do
64
+ ActiveRecord::Base.transaction do
65
+ block.call
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveEventStore # :nodoc:
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_event_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-14 00:00:00.000000000 Z
11
+ date: 2021-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails_event_store
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '5.0'
83
97
  description: Wrapper over Rails Event Store with conventions and transparent Rails
84
98
  integration
85
99
  email:
@@ -101,6 +115,8 @@ files:
101
115
  - lib/active_event_store/rspec/have_enqueued_async_subscriber_for.rb
102
116
  - lib/active_event_store/rspec/have_published_event.rb
103
117
  - lib/active_event_store/subscriber_job.rb
118
+ - lib/active_event_store/test_helper.rb
119
+ - lib/active_event_store/test_helper/event_published_matcher.rb
104
120
  - lib/active_event_store/version.rb
105
121
  homepage: http://github.com/palkan/active_event_store
106
122
  licenses: