ruby_event_store-rspec 2.1.0 → 2.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1362df53ba19bf0a2194492d627ab17cd22ee658c9fefa47b5c61c0b8eec562
4
- data.tar.gz: c665c377cf751768cb1ec6abd8f486231ae382c6dafaef36e8cf0aaf01bc3800
3
+ metadata.gz: 8eb0afc118a3be2829fec2383858efaf7c8a1766a41db876e39969261b5dc017
4
+ data.tar.gz: a6c84fcff753ba1753eccda8029cff733885f4cd383bcd04a4976a591cf3c2d1
5
5
  SHA512:
6
- metadata.gz: 94e802c2bbed41e35aa8f8a2507cc127afe2e853e2266d60a56ea240f98c09f357bf7c664e7489de95a3f69d0b4892cd7f50a1c261fe2e7c995b531b45518fe4
7
- data.tar.gz: bf4cc3ec24389daf273187565731d7dc460e2d2ba9621605dafbbce46b19df9324f27cc98deb8c749955e15b10a3c6dd8167f36ae07fbea0592a2c2b8e884261
6
+ metadata.gz: 6f7db18e2cef98b5af008f636bc04fbdbb89763eb5eec488fa9fdc439d95793329c34e8ce17d2b1f0bd5423a767772e2c1dfae34867e3ff8b87c5c7d9de1a44f
7
+ data.tar.gz: 28be5f30b48d48b8d643f75afde951a706a96fbac8b3d3472f8e890e42c2e2ba2b02e78f6fd5836982021d4b601f02eb98da09a09d13497830f898a0d6d35e07
data/README.md CHANGED
@@ -3,4 +3,3 @@
3
3
  RSpec matchers for RubyEventStore.
4
4
 
5
5
  Find out more at [https://railseventstore.org](https://railseventstore.org/)
6
-
@@ -3,58 +3,51 @@
3
3
  module RubyEventStore
4
4
  module RSpec
5
5
  class Apply
6
+ def initialize(*expected, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @failure_message_formatter = failure_message_formatter
9
+ @fetch_events = FetchUnpublishedEvents.new
10
+ end
11
+
6
12
  def in(aggregate)
7
- @aggregate = aggregate
13
+ fetch_events.in(aggregate)
8
14
  self
9
15
  end
10
16
 
11
17
  def strict
12
- @matcher = ::RSpec::Matchers::BuiltIn::Match.new(@expected)
18
+ expected.strict
13
19
  self
14
20
  end
15
21
 
16
- def matches?(event_proc)
17
- raise_aggregate_not_set unless @aggregate
18
- before = @aggregate.unpublished_events.to_a
19
- event_proc.call
20
- @applied_events = @aggregate.unpublished_events.to_a - before
21
- if match_events?
22
- @matcher.matches?(@applied_events)
23
- else
24
- !@applied_events.empty?
25
- end
22
+ def exactly(count)
23
+ expected.exactly(count)
24
+ self
26
25
  end
27
26
 
28
- def failure_message
29
- if match_events?
30
- <<-EOS
31
- expected block to have applied:
27
+ def times
28
+ self
29
+ end
30
+ alias time times
32
31
 
33
- #{@expected}
32
+ def once
33
+ expected.once
34
+ self
35
+ end
34
36
 
35
- but applied:
37
+ def matches?(event_proc)
38
+ raise_aggregate_not_set unless fetch_events.aggregate?
39
+ before = fetch_events.aggregate.unpublished_events.to_a
40
+ event_proc.call
41
+ @applied_events = fetch_events.aggregate.unpublished_events.to_a - before
42
+ MatchEvents.new.call(expected, applied_events)
43
+ end
36
44
 
37
- #{@applied_events}
38
- EOS
39
- else
40
- "expected block to have applied any events"
41
- end
45
+ def failure_message
46
+ failure_message_formatter.failure_message(expected, applied_events)
42
47
  end
43
48
 
44
49
  def failure_message_when_negated
45
- if match_events?
46
- <<-EOS
47
- expected block not to have applied:
48
-
49
- #{@expected}
50
-
51
- but applied:
52
-
53
- #{@applied_events}
54
- EOS
55
- else
56
- "expected block not to have applied any events"
57
- end
50
+ failure_message_formatter.failure_message_when_negated(expected, applied_events)
58
51
  end
59
52
 
60
53
  def description
@@ -67,18 +60,11 @@ EOS
67
60
 
68
61
  private
69
62
 
70
- def initialize(*expected)
71
- @expected = expected
72
- @matcher = ::RSpec::Matchers::BuiltIn::Include.new(*expected)
73
- end
74
-
75
- def match_events?
76
- !@expected.empty?
77
- end
78
-
79
63
  def raise_aggregate_not_set
80
- raise SyntaxError, "You have to set the aggregate instance with `in`, e.g. `expect { ... }.to apply(an_event(MyEvent)).in(aggregate)`"
64
+ raise "You have to set the aggregate instance with `in`, e.g. `expect { ... }.to apply(an_event(MyEvent)).in(aggregate)`"
81
65
  end
66
+
67
+ attr_reader :expected, :applied_events, :failure_message_formatter, :fetch_events
82
68
  end
83
69
  end
84
70
  end
@@ -29,9 +29,9 @@ module RubyEventStore
29
29
  class FailureMessage
30
30
  class ExpectedLine
31
31
  def initialize(expected_klass, expected_metadata, expected_data)
32
- @expected_klass = expected_klass
32
+ @expected_klass = expected_klass
33
33
  @expected_metadata = expected_metadata
34
- @expected_data = expected_data
34
+ @expected_data = expected_data
35
35
  end
36
36
 
37
37
  def to_s
@@ -55,11 +55,11 @@ module RubyEventStore
55
55
 
56
56
  class ActualLine
57
57
  def initialize(actual_klass, actual_metadata, actual_data, expected_metadata, expected_data)
58
- @actual_klass = actual_klass
59
- @actual_metadata = actual_metadata
60
- @actual_data = actual_data
58
+ @actual_klass = actual_klass
59
+ @actual_metadata = actual_metadata
60
+ @actual_data = actual_data
61
61
  @expected_metadata = expected_metadata
62
- @expected_data = expected_data
62
+ @expected_data = expected_data
63
63
  end
64
64
 
65
65
  def to_s
@@ -83,25 +83,33 @@ module RubyEventStore
83
83
 
84
84
  class Diff
85
85
  def initialize(actual, expected, label, differ:)
86
- @actual = actual
86
+ @actual = actual
87
87
  @expected = expected
88
- @label = label
89
- @differ = differ
88
+ @label = label
89
+ @differ = differ
90
90
  end
91
91
 
92
92
  def to_s
93
- @expected && ["\n#{@label} diff:", @differ.diff_as_string(@actual.to_s, @expected.to_s)]
93
+ @expected && ["\n#{@label} diff:", @differ.diff(@actual.to_s + "\n", @expected.to_s)]
94
94
  end
95
95
  end
96
96
 
97
- def initialize(expected_klass, actual_klass, expected_data, actual_data, expected_metadata, actual_metadata, differ:)
98
- @expected_klass = expected_klass
99
- @actual_klass = actual_klass
100
- @expected_data = expected_data
101
- @actual_data = actual_data
97
+ def initialize(
98
+ expected_klass,
99
+ actual_klass,
100
+ expected_data,
101
+ actual_data,
102
+ expected_metadata,
103
+ actual_metadata,
104
+ differ:
105
+ )
106
+ @expected_klass = expected_klass
107
+ @actual_klass = actual_klass
108
+ @expected_data = expected_data
109
+ @actual_data = actual_data
102
110
  @expected_metadata = expected_metadata
103
- @actual_metadata = actual_metadata
104
- @differ = differ
111
+ @actual_metadata = actual_metadata
112
+ @differ = differ
105
113
  end
106
114
 
107
115
  def to_s
@@ -117,14 +125,14 @@ module RubyEventStore
117
125
  include ::RSpec::Matchers::Composable
118
126
 
119
127
  def initialize(expected, differ:, formatter:)
120
- @expected = expected
121
- @differ = differ
128
+ @expected = expected
129
+ @differ = differ
122
130
  @formatter = formatter
123
131
  end
124
132
 
125
133
  def matches?(actual)
126
134
  @actual = actual
127
- matches_kind && matches_data && matches_metadata
135
+ matches_kind?(actual) && matches_data?(actual) && matches_metadata?(actual)
128
136
  end
129
137
 
130
138
  def with_data(expected_data)
@@ -138,16 +146,24 @@ module RubyEventStore
138
146
  end
139
147
 
140
148
  def failure_message
141
- actual_data = actual.data if actual.respond_to?(:data)
149
+ actual_data = actual.data if actual.respond_to?(:data)
142
150
  actual_metadata = actual.metadata if actual.respond_to?(:metadata)
143
- FailureMessage.new(expected, actual.class, expected_data, actual_data, expected_metadata, actual_metadata, differ: differ).to_s
151
+ FailureMessage.new(
152
+ expected,
153
+ actual.class,
154
+ expected_data,
155
+ actual_data,
156
+ expected_metadata,
157
+ actual_metadata,
158
+ differ: differ
159
+ ).to_s
144
160
  end
145
161
 
146
162
  def failure_message_when_negated
147
- %Q{
163
+ "
148
164
  expected: not a kind of #{expected}
149
165
  got: #{actual.class}
150
- }
166
+ "
151
167
  end
152
168
 
153
169
  def strict
@@ -167,26 +183,27 @@ expected: not a kind of #{expected}
167
183
  " (#{expectation_list.join(" and ")})" if expectation_list.any?
168
184
  end
169
185
 
170
- private
186
+ attr_reader :expected, :expected_data, :expected_metadata
171
187
 
172
- def matches_kind
173
- KindMatcher.new(expected).matches?(actual)
188
+ def strict?
189
+ @strict
174
190
  end
175
191
 
176
- def matches_data
177
- DataMatcher.new(expected_data, strict: strict?).matches?(actual.data)
192
+ def matches_kind?(actual_event)
193
+ KindMatcher.new(expected).matches?(actual_event)
178
194
  end
179
195
 
180
- def matches_metadata
181
- DataMatcher.new(expected_metadata, strict: strict?).matches?(actual.metadata.to_h)
182
- end
196
+ private
183
197
 
184
- attr_reader :expected_metadata, :expected_data, :actual, :expected, :differ, :formatter
198
+ def matches_data?(actual_event)
199
+ DataMatcher.new(expected_data, strict: strict?).matches?(actual_event.data)
200
+ end
185
201
 
186
- def strict?
187
- @strict
202
+ def matches_metadata?(actual_event)
203
+ DataMatcher.new(expected_metadata, strict: strict?).matches?(actual_event.metadata.to_h)
188
204
  end
205
+
206
+ attr_reader :actual, :differ, :formatter
189
207
  end
190
208
  end
191
209
  end
192
-
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module RSpec
5
+ class CrudeFailureMessageFormatter
6
+ class HavePublished
7
+ def initialize(differ)
8
+ @differ = differ
9
+ end
10
+
11
+ def failure_message(expected, events, stream_name)
12
+ stream_expectation = " in stream #{stream_name}" unless stream_name.nil?
13
+ "expected #{expected.events} to be published#{stream_expectation}, diff:" +
14
+ differ.diff(expected.events.to_s + "\n", events)
15
+ end
16
+
17
+ def failure_message_when_negated(expected, events, stream_name)
18
+ stream_expectation = " in stream #{stream_name}" unless stream_name.nil?
19
+ "expected #{expected.events} not to be published#{stream_expectation}, diff:" +
20
+ differ.diff(expected.events.to_s + "\n", events)
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :differ
26
+ end
27
+
28
+ class Publish
29
+ def failure_message(expected, events, stream)
30
+ if !expected.empty?
31
+ <<~EOS
32
+ expected block to have published:
33
+
34
+ #{expected.events}
35
+
36
+ #{"in stream #{stream} " if stream}but published:
37
+
38
+ #{events}
39
+ EOS
40
+ else
41
+ "expected block to have published any events"
42
+ end
43
+ end
44
+
45
+ def failure_message_when_negated(expected, events, stream)
46
+ if !expected.empty?
47
+ <<~EOS
48
+ expected block not to have published:
49
+
50
+ #{expected.events}
51
+
52
+ #{"in stream #{stream} " if stream}but published:
53
+
54
+ #{events}
55
+ EOS
56
+ else
57
+ "expected block not to have published any events"
58
+ end
59
+ end
60
+ end
61
+
62
+ class HaveApplied
63
+ def initialize(differ)
64
+ @differ = differ
65
+ end
66
+
67
+ def failure_message(expected, events)
68
+ "expected #{expected.events} to be applied, diff:" + differ.diff(expected.events.to_s + "\n", events)
69
+ end
70
+
71
+ def failure_message_when_negated(expected, events)
72
+ "expected #{expected.events} not to be applied, diff:" + differ.diff(expected.events.inspect + "\n", events)
73
+ end
74
+
75
+ attr_reader :differ
76
+ end
77
+
78
+ class Apply
79
+ def failure_message(expected, applied_events)
80
+ if !expected.empty?
81
+ <<~EOS
82
+ expected block to have applied:
83
+
84
+ #{expected.events}
85
+
86
+ but applied:
87
+
88
+ #{applied_events}
89
+ EOS
90
+ else
91
+ "expected block to have applied any events"
92
+ end
93
+ end
94
+
95
+ def failure_message_when_negated(expected, applied_events)
96
+ if !expected.empty?
97
+ <<~EOS
98
+ expected block not to have applied:
99
+
100
+ #{expected.events}
101
+
102
+ but applied:
103
+
104
+ #{applied_events}
105
+ EOS
106
+ else
107
+ "expected block not to have applied any events"
108
+ end
109
+ end
110
+ end
111
+
112
+ def have_published(differ)
113
+ HavePublished.new(differ)
114
+ end
115
+
116
+ def publish(_differ)
117
+ Publish.new
118
+ end
119
+
120
+ def have_applied(differ)
121
+ HaveApplied.new(differ)
122
+ end
123
+
124
+ def apply(_differ)
125
+ Apply.new
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,43 @@
1
+ module RubyEventStore
2
+ module RSpec
3
+ class ExpectedCollection
4
+ def initialize(events)
5
+ @events = events
6
+ @strict = false
7
+ end
8
+
9
+ def exactly(count)
10
+ raise NotSupported if !events.size.equal?(1)
11
+ raise NotSupported if count < 1
12
+ @count = count
13
+ end
14
+
15
+ def empty?
16
+ events.empty?
17
+ end
18
+
19
+ def once
20
+ exactly(1)
21
+ end
22
+
23
+ def specified_count?
24
+ !count.nil?
25
+ end
26
+
27
+ def strict
28
+ @strict = true
29
+ end
30
+
31
+ def strict?
32
+ @strict
33
+ end
34
+
35
+ def event
36
+ raise NotSupported if !events.size.equal?(1)
37
+ events.first
38
+ end
39
+
40
+ attr_reader :events, :count
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ module RubyEventStore
2
+ module RSpec
3
+ class FetchEvents
4
+ MissingEventStore = Class.new(StandardError)
5
+
6
+ def from(event_id)
7
+ @start = event_id
8
+ end
9
+
10
+ def stream(stream_name)
11
+ @stream_name = stream_name
12
+ end
13
+
14
+ def in(event_store)
15
+ @event_store = event_store
16
+ end
17
+
18
+ def from_last
19
+ @start = call.last&.event_id
20
+ end
21
+
22
+ def call
23
+ raise MissingEventStore if event_store.nil?
24
+ events = event_store.read
25
+ events = events.stream(stream_name) if stream_name
26
+ events = events.from(start) if start
27
+ events
28
+ end
29
+
30
+ attr_reader :start, :stream_name, :event_store
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ module RubyEventStore
2
+ module RSpec
3
+ class FetchUnpublishedEvents
4
+ def in(aggregate)
5
+ @aggregate = aggregate
6
+ end
7
+
8
+ def call
9
+ aggregate.unpublished_events.to_a
10
+ end
11
+
12
+ def aggregate?
13
+ !aggregate.nil?
14
+ end
15
+
16
+ attr_reader :aggregate
17
+ end
18
+ end
19
+ end
@@ -3,61 +3,54 @@
3
3
  module RubyEventStore
4
4
  module RSpec
5
5
  class HaveApplied
6
- def initialize(mandatory_expected, *optional_expected, differ:, phraser:)
7
- @expected = [mandatory_expected, *optional_expected]
8
- @matcher = ::RSpec::Matchers::BuiltIn::Include.new(*expected)
9
- @differ = differ
10
- @phraser = phraser
6
+ def initialize(*expected, phraser:, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @failure_message_formatter = failure_message_formatter
9
+ @phraser = phraser
10
+ @fetch_events = FetchUnpublishedEvents.new
11
11
  end
12
12
 
13
13
  def matches?(aggregate_root)
14
- @events = aggregate_root.unpublished_events.to_a
15
- matcher.matches?(events) && matches_count?
14
+ fetch_events.in(aggregate_root)
15
+ @events = fetch_events.call
16
+ MatchEvents.new.call(expected, events)
16
17
  end
17
18
 
18
19
  def exactly(count)
19
- @count = count
20
+ expected.exactly(count)
20
21
  self
21
22
  end
22
23
 
23
24
  def times
24
25
  self
25
26
  end
26
- alias :time :times
27
+ alias time times
27
28
 
28
29
  def once
29
- exactly(1)
30
+ expected.once
31
+ self
30
32
  end
31
33
 
32
34
  def strict
33
- @matcher = ::RSpec::Matchers::BuiltIn::Match.new(expected)
35
+ expected.strict
34
36
  self
35
37
  end
36
38
 
37
39
  def failure_message
38
- "expected #{expected} to be applied, diff:" +
39
- differ.diff_as_string(expected.to_s, events.to_s)
40
+ failure_message_formatter.failure_message(expected, events)
40
41
  end
41
42
 
42
43
  def failure_message_when_negated
43
- "expected #{expected} not to be applied, diff:" +
44
- differ.diff_as_string(expected.inspect, events.inspect)
44
+ failure_message_formatter.failure_message_when_negated(expected, events)
45
45
  end
46
46
 
47
47
  def description
48
- "have applied events that have to (#{phraser.(expected)})"
48
+ "have applied events that have to (#{phraser.(expected.events)})"
49
49
  end
50
50
 
51
51
  private
52
52
 
53
- def matches_count?
54
- return true unless count
55
- raise NotSupported if expected.size > 1
56
- events.select { |e| expected.first === e }.size.equal?(count)
57
- end
58
-
59
- attr_reader :differ, :phraser, :expected, :events, :count, :matcher
53
+ attr_reader :phraser, :expected, :events, :failure_message_formatter, :fetch_events
60
54
  end
61
55
  end
62
56
  end
63
-
@@ -3,73 +3,77 @@
3
3
  module RubyEventStore
4
4
  module RSpec
5
5
  class HavePublished
6
- def initialize(mandatory_expected, *optional_expected, differ:, phraser:)
7
- @expected = [mandatory_expected, *optional_expected]
8
- @matcher = ::RSpec::Matchers::BuiltIn::Include.new(*expected)
9
- @differ = differ
10
- @phraser = phraser
6
+ def initialize(*expected, phraser:, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @phraser = phraser
9
+ @failure_message_formatter = failure_message_formatter
10
+ @fetch_events = FetchEvents.new
11
11
  end
12
12
 
13
13
  def matches?(event_store)
14
- @events = event_store.read
15
- @events = events.stream(stream_name) if stream_name
16
- @events = events.from(start) if start
17
- @events = events.each
18
- @matcher.matches?(events) && matches_count?
14
+ stream_names.all? do |stream_name|
15
+ fetch_events.stream(stream_name)
16
+ fetch_events.in(event_store)
17
+ @published_events = fetch_events.call.to_a
18
+ @failed_on_stream = stream_name
19
+ MatchEvents.new.call(expected, published_events)
20
+ end
19
21
  end
20
22
 
21
23
  def exactly(count)
22
- @count = count
24
+ expected.exactly(count)
23
25
  self
24
26
  end
25
27
 
26
28
  def in_stream(stream_name)
27
- @stream_name = stream_name
29
+ @stream_names = [stream_name]
30
+ self
31
+ end
32
+
33
+ def in_streams(stream_names)
34
+ @stream_names = Array(stream_names)
28
35
  self
29
36
  end
30
37
 
31
38
  def times
32
39
  self
33
40
  end
34
- alias :time :times
41
+ alias time times
35
42
 
36
43
  def from(event_id)
37
- @start = event_id
44
+ fetch_events.from(event_id)
38
45
  self
39
46
  end
40
47
 
41
48
  def once
42
- exactly(1)
49
+ expected.once
50
+ self
43
51
  end
44
52
 
45
53
  def failure_message
46
- "expected #{expected} to be published, diff:" +
47
- differ.diff_as_string(expected.to_s, events.to_a.to_s)
54
+ failure_message_formatter.failure_message(expected, published_events, failed_on_stream)
48
55
  end
49
56
 
50
57
  def failure_message_when_negated
51
- "expected #{expected} not to be published, diff:" +
52
- differ.diff_as_string(expected.to_s, events.to_a.to_s)
58
+ failure_message_formatter.failure_message_when_negated(expected, published_events, failed_on_stream)
53
59
  end
54
60
 
55
61
  def description
56
- "have published events that have to (#{phraser.(expected)})"
62
+ "have published events that have to (#{phraser.(expected.events)})"
57
63
  end
58
64
 
59
65
  def strict
60
- @matcher = ::RSpec::Matchers::BuiltIn::Match.new(expected)
66
+ expected.strict
61
67
  self
62
68
  end
63
69
 
64
70
  private
65
71
 
66
- def matches_count?
67
- return true unless count
68
- raise NotSupported if expected.size > 1
69
- events.select { |e| expected.first === e }.size.equal?(count)
72
+ def stream_names
73
+ @stream_names || [nil]
70
74
  end
71
75
 
72
- attr_reader :differ, :phraser, :stream_name, :expected, :count, :events, :start
76
+ attr_reader :phraser, :expected, :published_events, :failed_on_stream, :failure_message_formatter, :fetch_events
73
77
  end
74
78
  end
75
79
  end
@@ -12,9 +12,7 @@ module RubyEventStore
12
12
 
13
13
  def matches?(handler)
14
14
  @handler = handler
15
- @subscribed_to = expected.select do |event|
16
- event_store.subscribers_for(event).include?(handler)
17
- end
15
+ @subscribed_to = expected.select { |event| event_store.subscribers_for(event).include?(handler) }
18
16
 
19
17
  matcher.matches?(subscribed_to)
20
18
  end
@@ -25,13 +23,11 @@ module RubyEventStore
25
23
  end
26
24
 
27
25
  def failure_message
28
- "expected #{handler} to be subscribed to events, diff:" +
29
- differ.diff_as_string(expected.to_s, subscribed_to.to_s)
26
+ "expected #{handler} to be subscribed to events, diff:" + differ.diff(expected.to_s + "\n", subscribed_to)
30
27
  end
31
28
 
32
29
  def failure_message_when_negated
33
- "expected #{handler} not to be subscribed to events, diff:" +
34
- differ.diff_as_string(expected.to_s, subscribed_to.to_s)
30
+ "expected #{handler} not to be subscribed to events, diff:" + differ.diff(expected.to_s + "\n", subscribed_to)
35
31
  end
36
32
 
37
33
  def description
@@ -40,9 +36,7 @@ module RubyEventStore
40
36
 
41
37
  private
42
38
 
43
- attr_reader :expected, :handler, :subscribed_to,
44
- :differ, :phraser, :matcher, :event_store
39
+ attr_reader :expected, :handler, :subscribed_to, :differ, :phraser, :matcher, :event_store
45
40
  end
46
41
  end
47
42
  end
48
-
@@ -0,0 +1,32 @@
1
+ module RubyEventStore
2
+ module RSpec
3
+ class MatchEvents
4
+ def call(expected, events)
5
+ if match_events?(expected)
6
+ matcher(expected).matches?(events) && matches_count?(expected, events)
7
+ else
8
+ !events.empty?
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def matches_count?(expected, events)
15
+ return true unless expected.specified_count?
16
+ events.select { |e| expected.event === e }.size.equal?(expected.count)
17
+ end
18
+
19
+ def matcher(expected)
20
+ if expected.strict?
21
+ ::RSpec::Matchers::BuiltIn::Match.new(expected.events)
22
+ else
23
+ ::RSpec::Matchers::BuiltIn::Include.new(*expected.events)
24
+ end
25
+ end
26
+
27
+ def match_events?(expected)
28
+ !expected.empty?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -8,11 +8,7 @@ module RubyEventStore
8
8
  def call(object)
9
9
  items = Array(object).compact.map { |o| format(o) }
10
10
  return "" if items.empty?
11
- if items.one?
12
- items.join
13
- else
14
- "#{items[all_but_last].join(", ")} and #{items.fetch(-1)}"
15
- end
11
+ items.one? ? items.join : "#{items[all_but_last].join(", ")} and #{items.fetch(-1)}"
16
12
  end
17
13
 
18
14
  private
@@ -22,11 +18,7 @@ module RubyEventStore
22
18
  end
23
19
 
24
20
  def format(object)
25
- if object.respond_to?(:description)
26
- ::RSpec::Support::ObjectFormatter.format(object)
27
- else
28
- "be a #{object}"
29
- end
21
+ object.respond_to?(:description) ? ::RSpec::Support::ObjectFormatter.format(object) : "be a #{object}"
30
22
  end
31
23
  end
32
24
  end
@@ -34,16 +26,24 @@ module RubyEventStore
34
26
  def be_an_event(expected)
35
27
  BeEvent.new(expected, differ: differ, formatter: formatter)
36
28
  end
37
- alias :be_event :be_an_event
38
- alias :an_event :be_an_event
39
- alias :event :be_an_event
29
+ alias be_event be_an_event
30
+ alias an_event be_an_event
31
+ alias event be_an_event
40
32
 
41
33
  def have_published(*expected)
42
- HavePublished.new(*expected, differ: differ, phraser: phraser)
34
+ HavePublished.new(
35
+ *expected,
36
+ phraser: phraser,
37
+ failure_message_formatter: RSpec.default_formatter.have_published(differ)
38
+ )
43
39
  end
44
40
 
45
41
  def have_applied(*expected)
46
- HaveApplied.new(*expected, differ: differ, phraser: phraser)
42
+ HaveApplied.new(
43
+ *expected,
44
+ phraser: phraser,
45
+ failure_message_formatter: RSpec.default_formatter.have_applied(differ)
46
+ )
47
47
  end
48
48
 
49
49
  def have_subscribed_to_events(*expected)
@@ -51,11 +51,11 @@ module RubyEventStore
51
51
  end
52
52
 
53
53
  def publish(*expected)
54
- Publish.new(*expected)
54
+ Publish.new(*expected, failure_message_formatter: RSpec.default_formatter.publish(differ))
55
55
  end
56
56
 
57
57
  def apply(*expected)
58
- Apply.new(*expected)
58
+ Apply.new(*expected, failure_message_formatter: RSpec.default_formatter.apply(differ))
59
59
  end
60
60
 
61
61
  private
@@ -65,7 +65,7 @@ module RubyEventStore
65
65
  end
66
66
 
67
67
  def differ
68
- ::RSpec::Support::Differ.new(color: ::RSpec::Matchers.configuration.color?)
68
+ ::RSpec::Expectations.differ
69
69
  end
70
70
 
71
71
  def phraser
@@ -3,61 +3,66 @@
3
3
  module RubyEventStore
4
4
  module RSpec
5
5
  class Publish
6
+ def initialize(*expected, failure_message_formatter:)
7
+ @expected = ExpectedCollection.new(expected)
8
+ @failure_message_formatter = failure_message_formatter
9
+ @fetch_events = FetchEvents.new
10
+ end
11
+
6
12
  def in(event_store)
7
- @event_store = event_store
13
+ fetch_events.in(event_store)
8
14
  self
9
15
  end
10
16
 
11
- def in_stream(stream)
12
- @stream = stream
17
+ def in_stream(stream_name)
18
+ @stream_names = [stream_name]
13
19
  self
14
20
  end
15
21
 
16
- def matches?(event_proc)
17
- raise_event_store_not_set unless @event_store
18
- spec = @event_store.read
19
- spec = spec.stream(@stream) if @stream
20
- last_event_before_block = spec.last
21
- event_proc.call
22
- spec = spec.from(last_event_before_block.event_id) if last_event_before_block
23
- @published_events = spec.to_a
24
- if match_events?
25
- ::RSpec::Matchers::BuiltIn::Include.new(*@expected).matches?(@published_events)
26
- else
27
- !@published_events.empty?
28
- end
22
+ def in_streams(stream_names)
23
+ @stream_names = Array(stream_names)
24
+ self
29
25
  end
30
26
 
31
- def failure_message
32
- if match_events?
33
- <<-EOS
34
- expected block to have published:
35
-
36
- #{@expected}
27
+ def exactly(count)
28
+ expected.exactly(count)
29
+ self
30
+ end
37
31
 
38
- #{"in stream #{@stream} " if @stream}but published:
32
+ def once
33
+ expected.once
34
+ self
35
+ end
39
36
 
40
- #{@published_events}
41
- EOS
42
- else
43
- "expected block to have published any events"
44
- end
37
+ def times
38
+ self
45
39
  end
40
+ alias time times
46
41
 
47
- def failure_message_when_negated
48
- if match_events?
49
- <<-EOS
50
- expected block not to have published:
42
+ def strict
43
+ expected.strict
44
+ self
45
+ end
51
46
 
52
- #{@expected}
47
+ def matches?(event_proc)
48
+ fetch_events.from_last
49
+ event_proc.call
50
+ stream_names.all? do |stream_name|
51
+ fetch_events.stream(stream_name)
52
+ @published_events = fetch_events.call.to_a
53
+ @failed_on_stream = stream_name
54
+ MatchEvents.new.call(expected, published_events)
55
+ end
56
+ rescue FetchEvents::MissingEventStore
57
+ raise "You have to set the event store instance with `in`, e.g. `expect { ... }.to publish(an_event(MyEvent)).in(event_store)`"
58
+ end
53
59
 
54
- #{"in stream #{@stream} " if @stream}but published:
60
+ def failure_message
61
+ failure_message_formatter.failure_message(expected, published_events, failed_on_stream)
62
+ end
55
63
 
56
- #{@published_events}
57
- EOS
58
- else
59
- "expected block not to have published any events"
60
- end
64
+ def failure_message_when_negated
65
+ failure_message_formatter.failure_message_when_negated(expected, published_events, fetch_events.stream_name)
61
66
  end
62
67
 
63
68
  def description
@@ -70,17 +75,11 @@ EOS
70
75
 
71
76
  private
72
77
 
73
- def initialize(*expected)
74
- @expected = expected
78
+ def stream_names
79
+ @stream_names || [nil]
75
80
  end
76
81
 
77
- def match_events?
78
- !@expected.empty?
79
- end
80
-
81
- def raise_event_store_not_set
82
- raise SyntaxError, "You have to set the event store instance with `in`, e.g. `expect { ... }.to publish(an_event(MyEvent)).in(event_store)`"
83
- end
82
+ attr_reader :fetch_events, :expected, :failure_message_formatter, :published_events, :failed_on_stream
84
83
  end
85
84
  end
86
85
  end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module RSpec
5
+ class StepByStepFailureMessageFormatter
6
+ class Lingo
7
+ def initialize(published)
8
+ @published = published
9
+ end
10
+ attr_reader :published
11
+ end
12
+
13
+ class HavePublished
14
+ def initialize(differ, lingo)
15
+ @differ = differ
16
+ @lingo = lingo
17
+ end
18
+
19
+ def failure_message(expected, events, stream_name = nil)
20
+ return failure_message_strict(expected, events) if expected.strict?
21
+ return failure_message_no_events if expected.empty?
22
+ expected.events.each do |expected_event|
23
+ correct_event_count = 0
24
+ events_with_correct_type = []
25
+ events.each do |actual_event|
26
+ if expected_event.matches?(actual_event)
27
+ correct_event_count += 1
28
+ elsif expected_event.matches_kind?(actual_event)
29
+ events_with_correct_type << actual_event
30
+ end
31
+ end
32
+
33
+ expectations = expected_message(expected, expected_event, stream_name)
34
+
35
+ if expected.specified_count?
36
+ if correct_event_count >= 1
37
+ reality = failure_message_incorrect_count(expected_event, events_with_correct_type, correct_event_count)
38
+ elsif !events_with_correct_type.empty?
39
+ reality = failure_message_correct_type_incorrect_payload(expected_event, events_with_correct_type)
40
+ else
41
+ reality = failure_message_incorrect_type
42
+ end
43
+ else
44
+ if correct_event_count >= 1
45
+ next
46
+ else
47
+ if !events_with_correct_type.empty?
48
+ reality = failure_message_correct_type_incorrect_payload(expected_event, events_with_correct_type)
49
+ else
50
+ reality = failure_message_incorrect_type
51
+ end
52
+ end
53
+ end
54
+
55
+ return expectations + reality
56
+ end
57
+ end
58
+
59
+ def failure_message_when_negated(expected, events, _stream_name = nil)
60
+ return failure_message_when_negated_no_events if expected.empty?
61
+ if expected.specified_count?
62
+ <<~EOS
63
+ expected
64
+ #{expected.events.first.description}
65
+ not to be #{lingo.published} exactly #{expected.count} times
66
+
67
+ #{actual_events_list(events)}
68
+ EOS
69
+ else
70
+ <<~EOS
71
+ expected #{expected_events_list(expected.events)} not to #{"exactly " if expected.strict?}be #{lingo.published}
72
+
73
+ #{actual_events_list(events)}
74
+ EOS
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ attr_reader :differ, :lingo
81
+
82
+ def failure_message_no_events
83
+ "expected anything to be #{lingo.published}\n"
84
+ end
85
+
86
+ def failure_message_when_negated_no_events
87
+ "expected something to be #{lingo.published}\n"
88
+ end
89
+
90
+ def failure_message_incorrect_count(expected_event, events_with_correct_type, correct_event_count)
91
+ [
92
+ <<~EOS,
93
+
94
+ but was #{lingo.published} #{correct_event_count} times
95
+ EOS
96
+ if !events_with_correct_type.empty?
97
+ [
98
+ <<~EOS.strip,
99
+ There are events of correct type but with incorrect payload:
100
+ EOS
101
+ events_with_correct_type.each_with_index.map do |event_with_correct_type, index|
102
+ event_diff(expected_event, event_with_correct_type, index)
103
+ end,
104
+ nil
105
+ ]
106
+ end
107
+ ].compact.join("\n")
108
+ end
109
+
110
+ def failure_message_correct_type_incorrect_payload(expected_event, events_with_correct_type)
111
+ <<~EOS
112
+ , but it was not #{lingo.published}
113
+
114
+ There are events of correct type but with incorrect payload:
115
+ #{events_with_correct_type.each_with_index.map { |event_with_correct_type, index| event_diff(expected_event, event_with_correct_type, index) }.join("\n")}
116
+ EOS
117
+ end
118
+
119
+ def event_diff(expected_event, event_with_correct_type, index)
120
+ [
121
+ "#{index + 1}) #{event_with_correct_type.inspect}",
122
+ indent(data_diff(expected_event, event_with_correct_type), 4),
123
+ indent(metadata_diff(expected_event, event_with_correct_type), 4)
124
+ ].reject(&:empty?)
125
+ end
126
+
127
+ def indent(str, count)
128
+ str.to_s.split("\n").map { |l| l.sub(//, " " * count) }
129
+ end
130
+
131
+ def failure_message_incorrect_type
132
+ <<~EOS
133
+ , but there is no event with such type
134
+ EOS
135
+ end
136
+
137
+ def failure_message_strict(expected, events)
138
+ if expected.specified_count?
139
+ <<~EOS
140
+ expected only
141
+ #{expected.events.first.description}
142
+ to be #{lingo.published} #{expected.count} times
143
+
144
+ #{actual_events_list(events)}
145
+ EOS
146
+ else
147
+ <<~EOS
148
+ expected only #{expected_events_list(expected.events)} to be #{lingo.published}
149
+
150
+ #{actual_events_list(events)}
151
+ EOS
152
+ end
153
+ end
154
+
155
+ def data_diff(expected_event, event_with_correct_type)
156
+ if !expected_event.expected_data.nil?
157
+ "data diff:#{differ.diff(expected_event.expected_data, event_with_correct_type.data)}"
158
+ end
159
+ end
160
+
161
+ def metadata_diff(expected_event, event_with_correct_type)
162
+ if !expected_event.expected_metadata.nil?
163
+ "metadata diff:#{differ.diff(expected_event.expected_metadata, event_with_correct_type.metadata.to_h)}"
164
+ end
165
+ end
166
+
167
+ def expected_message(expected, expected_event, stream_name)
168
+ expected_stream = " in stream #{stream_name}" if stream_name
169
+ if expected.specified_count?
170
+ <<~EOS
171
+ expected event
172
+ #{expected_event.description}
173
+ to be #{lingo.published} #{expected.count} times#{expected_stream}
174
+ EOS
175
+ else
176
+ <<~EOS
177
+ expected #{expected_events_list(expected.events)} to be #{lingo.published}#{expected_stream}
178
+
179
+ i.e. expected event
180
+ #{expected_event.description}
181
+ to be #{lingo.published}
182
+ EOS
183
+ end.strip
184
+ end
185
+
186
+ def expected_events_list(expected)
187
+ <<~EOS.strip
188
+ [
189
+ #{expected.map(&:description).map { |d| indent(d, 2) }.join("\n")}
190
+ ]
191
+ EOS
192
+ end
193
+
194
+ def actual_events_list(actual)
195
+ <<~EOS.strip
196
+ but the following was #{lingo.published}: [
197
+ #{actual.map(&:inspect).map { |d| indent(d, 2) }.join("\n")}
198
+ ]
199
+ EOS
200
+ end
201
+ end
202
+
203
+ def have_published(differ)
204
+ HavePublished.new(differ, Lingo.new("published"))
205
+ end
206
+
207
+ def publish(differ)
208
+ HavePublished.new(differ, Lingo.new("published"))
209
+ end
210
+
211
+ def have_applied(differ)
212
+ HavePublished.new(differ, Lingo.new("applied"))
213
+ end
214
+
215
+ def apply(differ)
216
+ HavePublished.new(differ, Lingo.new("applied"))
217
+ end
218
+ end
219
+ end
220
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module RSpec
5
- VERSION = "2.1.0"
5
+ VERSION = "2.4.0"
6
6
  end
7
7
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rspec'
3
+ require "rspec"
4
4
 
5
5
  module RubyEventStore
6
6
  module RSpec
@@ -8,15 +8,31 @@ module RubyEventStore
8
8
  end
9
9
  end
10
10
 
11
- require "ruby_event_store/rspec/version"
12
- require "ruby_event_store/rspec/be_event"
13
- require "ruby_event_store/rspec/have_published"
14
- require "ruby_event_store/rspec/have_applied"
15
- require "ruby_event_store/rspec/have_subscribed_to_events"
16
- require "ruby_event_store/rspec/publish"
17
- require "ruby_event_store/rspec/apply"
18
- require "ruby_event_store/rspec/matchers"
11
+ require_relative "rspec/version"
12
+ require_relative "rspec/be_event"
13
+ require_relative "rspec/expected_collection"
14
+ require_relative "rspec/fetch_events"
15
+ require_relative "rspec/fetch_unpublished_events"
16
+ require_relative "rspec/match_events"
17
+ require_relative "rspec/have_published"
18
+ require_relative "rspec/have_applied"
19
+ require_relative "rspec/have_subscribed_to_events"
20
+ require_relative "rspec/publish"
21
+ require_relative "rspec/apply"
22
+ require_relative "rspec/crude_failure_message_formatter"
23
+ require_relative "rspec/step_by_step_failure_message_formatter"
24
+ require_relative "rspec/matchers"
19
25
 
20
- ::RSpec.configure do |config|
21
- config.include ::RubyEventStore::RSpec::Matchers
26
+ module RubyEventStore
27
+ module RSpec
28
+ def self.default_formatter=(new_formatter)
29
+ @@default_formatter = new_formatter
30
+ end
31
+
32
+ def self.default_formatter
33
+ @@default_formatter ||= CrudeFailureMessageFormatter.new
34
+ end
35
+ end
22
36
  end
37
+
38
+ ::RSpec.configure { |config| config.include ::RubyEventStore::RSpec::Matchers }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_event_store-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkency
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-07 00:00:00.000000000 Z
11
+ date: 2022-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -35,11 +35,17 @@ files:
35
35
  - lib/ruby_event_store/rspec.rb
36
36
  - lib/ruby_event_store/rspec/apply.rb
37
37
  - lib/ruby_event_store/rspec/be_event.rb
38
+ - lib/ruby_event_store/rspec/crude_failure_message_formatter.rb
39
+ - lib/ruby_event_store/rspec/expected_collection.rb
40
+ - lib/ruby_event_store/rspec/fetch_events.rb
41
+ - lib/ruby_event_store/rspec/fetch_unpublished_events.rb
38
42
  - lib/ruby_event_store/rspec/have_applied.rb
39
43
  - lib/ruby_event_store/rspec/have_published.rb
40
44
  - lib/ruby_event_store/rspec/have_subscribed_to_events.rb
45
+ - lib/ruby_event_store/rspec/match_events.rb
41
46
  - lib/ruby_event_store/rspec/matchers.rb
42
47
  - lib/ruby_event_store/rspec/publish.rb
48
+ - lib/ruby_event_store/rspec/step_by_step_failure_message_formatter.rb
43
49
  - lib/ruby_event_store/rspec/version.rb
44
50
  homepage: https://railseventstore.org
45
51
  licenses:
@@ -49,6 +55,7 @@ metadata:
49
55
  changelog_uri: https://github.com/RailsEventStore/rails_event_store/releases
50
56
  source_code_uri: https://github.com/RailsEventStore/rails_event_store
51
57
  bug_tracker_uri: https://github.com/RailsEventStore/rails_event_store/issues
58
+ rubygems_mfa_required: 'true'
52
59
  post_install_message:
53
60
  rdoc_options: []
54
61
  require_paths:
@@ -57,14 +64,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
64
  requirements:
58
65
  - - ">="
59
66
  - !ruby/object:Gem::Version
60
- version: '2.5'
67
+ version: '2.7'
61
68
  required_rubygems_version: !ruby/object:Gem::Requirement
62
69
  requirements:
63
70
  - - ">="
64
71
  - !ruby/object:Gem::Version
65
72
  version: '0'
66
73
  requirements: []
67
- rubygems_version: 3.1.4
74
+ rubygems_version: 3.3.7
68
75
  signing_key:
69
76
  specification_version: 4
70
77
  summary: RSpec matchers for RubyEventStore