replay 0.0.1 → 0.1.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 +7 -0
- data/.rvmrc +1 -1
- data/Gemfile +7 -3
- data/Guardfile +10 -0
- data/LICENSE +21 -0
- data/README.md +153 -0
- data/Rakefile +15 -0
- data/lib/replay.rb +39 -10
- data/lib/replay/backends.rb +50 -0
- data/lib/replay/event_declarations.rb +36 -0
- data/lib/replay/event_decorator.rb +13 -0
- data/lib/replay/events.rb +24 -0
- data/lib/replay/inflector.rb +55 -0
- data/lib/replay/observer.rb +18 -0
- data/lib/replay/publisher.rb +72 -0
- data/lib/replay/repository.rb +61 -0
- data/lib/replay/repository/configuration.rb +30 -0
- data/lib/replay/repository/identity_map.rb +25 -0
- data/lib/replay/router.rb +5 -0
- data/lib/replay/router/default_router.rb +21 -0
- data/lib/replay/rspec.rb +50 -0
- data/lib/replay/subscription_manager.rb +28 -0
- data/lib/replay/test.rb +64 -0
- data/lib/replay/test/test_event_stream.rb +19 -0
- data/lib/replay/version.rb +1 -1
- data/proofs/all.rb +7 -0
- data/proofs/proofs_init.rb +10 -0
- data/proofs/replay/inflector_proof.rb +32 -0
- data/proofs/replay/publisher_proof.rb +170 -0
- data/proofs/replay/repository_configuration_proof.rb +67 -0
- data/proofs/replay/repository_proof.rb +46 -0
- data/proofs/replay/subscriber_manager_proof.rb +39 -0
- data/proofs/replay/test_proof.rb +28 -0
- data/replay.gemspec +5 -4
- data/test/replay/observer_spec.rb +37 -0
- data/test/replay/router/default_router_spec.rb +43 -0
- data/test/test_helper.rb +10 -0
- metadata +65 -48
- data/README +0 -27
- data/lib/replay/active_record_event_store.rb +0 -32
- data/lib/replay/domain.rb +0 -33
- data/lib/replay/event.rb +0 -27
- data/lib/replay/event_store.rb +0 -55
- data/lib/replay/projector.rb +0 -19
- data/lib/replay/test_storage.rb +0 -8
- data/lib/replay/unknown_event_error.rb +0 -2
- data/test/spec_helper.rb +0 -6
- data/test/test_events.sqlite3 +0 -0
- data/test/unit/active_record_event_store_spec.rb +0 -24
- data/test/unit/domain_spec.rb +0 -53
- data/test/unit/event_spec.rb +0 -13
- data/test/unit/event_store_spec.rb +0 -28
- data/test/unit/projector_spec.rb +0 -19
@@ -0,0 +1,170 @@
|
|
1
|
+
require_relative "../proofs_init.rb"
|
2
|
+
require 'replay/test'
|
3
|
+
|
4
|
+
class ReplayTest
|
5
|
+
include Replay::Publisher
|
6
|
+
include Replay::EventExaminer
|
7
|
+
|
8
|
+
key :pkey
|
9
|
+
|
10
|
+
def initialize(pkey = 1)
|
11
|
+
@pkey = pkey
|
12
|
+
end
|
13
|
+
|
14
|
+
events do
|
15
|
+
SomeEvent(pid: Integer)
|
16
|
+
UnhandledEvent(pid: Integer)
|
17
|
+
end
|
18
|
+
|
19
|
+
apply SomeEvent do |event|
|
20
|
+
@event_count ||= 0
|
21
|
+
@event_applied = event.pid
|
22
|
+
@event_count += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def pkey
|
26
|
+
@pkey
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module ReplayTest::Proof
|
31
|
+
def sets_publish_time
|
32
|
+
publish SomeEvent(pid: 123)
|
33
|
+
events.last.published_at != nil && (Time.now - events.last.published_at) < 20
|
34
|
+
end
|
35
|
+
|
36
|
+
def published_at_not_considered_in_equality
|
37
|
+
event = SomeEvent(pid: 123)
|
38
|
+
event == event.with(:published_at => Time.now-100)
|
39
|
+
end
|
40
|
+
|
41
|
+
def defines_events?
|
42
|
+
self.class.const_defined?(:SomeEvent) && self.class.const_get(:SomeEvent).is_a?(Class)
|
43
|
+
end
|
44
|
+
def adds_convenience_method?
|
45
|
+
respond_to? :SomeEvent
|
46
|
+
end
|
47
|
+
def applies_event?(event)
|
48
|
+
apply(event)
|
49
|
+
@event_applied == event.pid
|
50
|
+
end
|
51
|
+
|
52
|
+
def applies_events?(events)
|
53
|
+
apply(events)
|
54
|
+
@event_count == events.count
|
55
|
+
end
|
56
|
+
|
57
|
+
def throws_unhandled_event?(raise_it = true)
|
58
|
+
begin
|
59
|
+
apply(UnhandledEvent(pid: 123), raise_it)
|
60
|
+
rescue Replay::UnhandledEventError => e
|
61
|
+
true && raise_it
|
62
|
+
rescue Exception
|
63
|
+
!raise_it || false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def can_publish_events?
|
68
|
+
event = SomeEvent(pid: 123)
|
69
|
+
publish(event)
|
70
|
+
events.detect{|e| e==event}
|
71
|
+
end
|
72
|
+
|
73
|
+
def subscribers_receive_events
|
74
|
+
sub = Class.new do
|
75
|
+
def published(stream, event)
|
76
|
+
@published = true
|
77
|
+
end
|
78
|
+
def published?; @published; end
|
79
|
+
end.new
|
80
|
+
add_subscriber(sub)
|
81
|
+
publish(ReplayTest::SomeEvent.new(pid: 123))
|
82
|
+
sub.published?
|
83
|
+
end
|
84
|
+
def subscribes()
|
85
|
+
sub = Class.new do
|
86
|
+
def published(stream, event)
|
87
|
+
@published = true
|
88
|
+
end
|
89
|
+
def published?; @published; end
|
90
|
+
end.new
|
91
|
+
add_subscriber(sub)
|
92
|
+
has_subscriber?(sub)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
title "Publisher"
|
97
|
+
|
98
|
+
proof "Defines events given in the events block" do
|
99
|
+
r = ReplayTest.new
|
100
|
+
r.prove{ defines_events? }
|
101
|
+
end
|
102
|
+
|
103
|
+
proof "Adds a convenience method for an event constructor to the class" do
|
104
|
+
r = ReplayTest.new
|
105
|
+
r.prove{ adds_convenience_method? }
|
106
|
+
end
|
107
|
+
|
108
|
+
proof "Applies events singly" do
|
109
|
+
r = ReplayTest.new
|
110
|
+
r.prove{ applies_event? ReplayTest::SomeEvent.new(:pid => 123)}
|
111
|
+
end
|
112
|
+
|
113
|
+
proof "Applies events in ordered batches" do
|
114
|
+
r = ReplayTest.new
|
115
|
+
r.prove do applies_events?([
|
116
|
+
ReplayTest::SomeEvent.new(:pid => 123),
|
117
|
+
ReplayTest::SomeEvent.new(:pid => 234),
|
118
|
+
ReplayTest::SomeEvent.new(:pid => 456)
|
119
|
+
])
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
proof "Throws an UnhandledEventError for unhandled events" do
|
124
|
+
r = ReplayTest.new
|
125
|
+
r.prove{ throws_unhandled_event? }
|
126
|
+
end
|
127
|
+
|
128
|
+
proof "Ignores unhandled events if requested" do
|
129
|
+
r = ReplayTest.new
|
130
|
+
r.prove{ throws_unhandled_event? false}
|
131
|
+
end
|
132
|
+
|
133
|
+
proof "Can publish events to the indicated stream" do
|
134
|
+
r = ReplayTest.new
|
135
|
+
r.prove { can_publish_events? }
|
136
|
+
end
|
137
|
+
|
138
|
+
proof "Subscriber can subscribe to events from publisher" do
|
139
|
+
r = ReplayTest.new
|
140
|
+
r.prove{ subscribes }
|
141
|
+
end
|
142
|
+
|
143
|
+
proof "Subscriber receives published events" do
|
144
|
+
r = ReplayTest.new
|
145
|
+
r.prove{ subscribers_receive_events }
|
146
|
+
end
|
147
|
+
|
148
|
+
proof "Returns self from apply" do
|
149
|
+
r = ReplayTest.new
|
150
|
+
r.prove{ apply([]) == self}
|
151
|
+
end
|
152
|
+
proof "Returns self from publish" do
|
153
|
+
r = ReplayTest.new
|
154
|
+
r.prove{ publish([]) == self}
|
155
|
+
end
|
156
|
+
|
157
|
+
proof "sets the publish time on events" do
|
158
|
+
r = ReplayTest.new
|
159
|
+
r.prove{ sets_publish_time }
|
160
|
+
end
|
161
|
+
|
162
|
+
proof "publish time is not part of equality" do
|
163
|
+
r = ReplayTest.new
|
164
|
+
r.prove{ published_at_not_considered_in_equality}
|
165
|
+
end
|
166
|
+
|
167
|
+
proof "Can implement initializer with arguments" do
|
168
|
+
r = ReplayTest.new(:foo)
|
169
|
+
r.prove { pkey == :foo }
|
170
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative "../proofs_init.rb"
|
2
|
+
require 'replay/test'
|
3
|
+
|
4
|
+
class Subscriber
|
5
|
+
def published(stream_id, event); end
|
6
|
+
end
|
7
|
+
|
8
|
+
module Replay::Repository::Configuration::Proof
|
9
|
+
def can_configure_store?
|
10
|
+
self.store = :memory
|
11
|
+
self.store == Replay::Backends::MemoryStore
|
12
|
+
end
|
13
|
+
|
14
|
+
def can_add_default_subscriber?
|
15
|
+
sub = Subscriber.new
|
16
|
+
self.add_default_subscriber sub
|
17
|
+
subscribers.include? sub
|
18
|
+
end
|
19
|
+
|
20
|
+
def subscribers_include_store
|
21
|
+
self.store = :memory
|
22
|
+
self.subscribers.include? Replay::Backends::MemoryStore
|
23
|
+
end
|
24
|
+
|
25
|
+
def requires_store_to_act_like_subscriber
|
26
|
+
begin
|
27
|
+
store = Class.new do
|
28
|
+
def self.event_stream(stream); end
|
29
|
+
end
|
30
|
+
self.store = store
|
31
|
+
rescue Replay::InvalidSubscriberError => e
|
32
|
+
return true
|
33
|
+
rescue Exception => e
|
34
|
+
end
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
def requires_store_to_load_events
|
39
|
+
begin
|
40
|
+
self.store = Subscriber.new
|
41
|
+
rescue Replay::InvalidStorageError
|
42
|
+
return true
|
43
|
+
rescue Exception => e
|
44
|
+
end
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
proof "can configure a store" do
|
50
|
+
Replay::Repository::Configuration.new.prove {can_configure_store?}
|
51
|
+
end
|
52
|
+
|
53
|
+
proof 'adding a store adds it as a subscriber' do
|
54
|
+
Replay::Repository::Configuration.new.prove { subscribers_include_store }
|
55
|
+
end
|
56
|
+
|
57
|
+
proof "can configure a default_subscriber" do
|
58
|
+
Replay::Repository::Configuration.new.prove {can_add_default_subscriber?}
|
59
|
+
end
|
60
|
+
|
61
|
+
proof "raises error if store won't load events" do
|
62
|
+
Replay::Repository::Configuration.new.prove {requires_store_to_load_events }
|
63
|
+
end
|
64
|
+
|
65
|
+
proof "raises error if store won't act like a subscriber" do
|
66
|
+
Replay::Repository::Configuration.new.prove {requires_store_to_act_like_subscriber }
|
67
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative "../proofs_init.rb"
|
2
|
+
require 'replay/test'
|
3
|
+
|
4
|
+
class Subscriber
|
5
|
+
def published(stream_id, event); end
|
6
|
+
end
|
7
|
+
|
8
|
+
class RepositoryTest
|
9
|
+
include Replay::Repository
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :proven
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.can_be_configured
|
17
|
+
self.configure do |config|
|
18
|
+
self.proven = true
|
19
|
+
end
|
20
|
+
|
21
|
+
self.proven
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
title "Repository interface"
|
26
|
+
|
27
|
+
proof "repository can be configured" do
|
28
|
+
RepositoryTest.prove {can_be_configured }
|
29
|
+
end
|
30
|
+
|
31
|
+
Proof::Output.class_exec do
|
32
|
+
writer :pending, :level => :info do |text|
|
33
|
+
prefix :pending, "PENDING: #{text}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def pending(text = "pending test")
|
38
|
+
Proof::Output.pending "pending test"
|
39
|
+
end
|
40
|
+
proof "loads an instance of supplied class" do; pending; end
|
41
|
+
proof "load raises an error if event stream isn't found" do; pending; end
|
42
|
+
proof "load returns uncreated instance when :create is pending" do; false; end
|
43
|
+
proof "load returns a created instance when :create is true" do; pending; end
|
44
|
+
|
45
|
+
proof "reload returns the supplied instance in its current state" do; pending; end
|
46
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative "../proofs_init.rb"
|
2
|
+
require 'replay/test'
|
3
|
+
|
4
|
+
module Replay::SubscriptionManager::Proof
|
5
|
+
def only_adds_legit_subs
|
6
|
+
begin
|
7
|
+
add_subscriber(Object.new)
|
8
|
+
rescue Replay::InvalidSubscriberError => e
|
9
|
+
return true
|
10
|
+
end
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def sub_gets_notified
|
15
|
+
sub = Class.new do
|
16
|
+
attr_accessor :stream, :event
|
17
|
+
def published(stream, event)
|
18
|
+
self.stream = stream
|
19
|
+
self.event = event
|
20
|
+
end
|
21
|
+
end.new
|
22
|
+
add_subscriber(sub)
|
23
|
+
notify_subscribers "123", "456"
|
24
|
+
sub.stream == '123' && sub.event == '456'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
title "Subscription Manager"
|
29
|
+
|
30
|
+
proof "raises InvalidSubscriberError when subscriber fails to implement #published" do
|
31
|
+
sm = Replay::SubscriptionManager.new
|
32
|
+
sm.prove { only_adds_legit_subs }
|
33
|
+
end
|
34
|
+
|
35
|
+
proof "notifies proper subscriber when informed" do
|
36
|
+
sm = Replay::SubscriptionManager.new
|
37
|
+
sm.prove { sub_gets_notified }
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative "../proofs_init.rb"
|
2
|
+
require 'replay/test'
|
3
|
+
|
4
|
+
desc "proof for the test support provided by replay"
|
5
|
+
|
6
|
+
title "Test support"
|
7
|
+
|
8
|
+
proof "fuzzy matching of events" do
|
9
|
+
class TestEvent
|
10
|
+
include Replay::EventDecorator
|
11
|
+
|
12
|
+
values do
|
13
|
+
attribute :one, String
|
14
|
+
attribute :two, String
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches_fuzzy(event)
|
18
|
+
self.kind_of_matches?(event)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
e1 = TestEvent.new(one: '1')
|
23
|
+
e2 = TestEvent.new(one: '1', two: '2')
|
24
|
+
|
25
|
+
e1.prove{ matches_fuzzy(e2)}
|
26
|
+
e2.prove{ !matches_fuzzy(e1)}
|
27
|
+
|
28
|
+
end
|
data/replay.gemspec
CHANGED
@@ -19,8 +19,9 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
# specify any dependencies here; for example:
|
22
|
-
s.add_development_dependency "
|
23
|
-
s.add_development_dependency "
|
24
|
-
|
25
|
-
s.add_runtime_dependency "
|
22
|
+
s.add_development_dependency "bundler", "~>1.3"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "minitest"
|
25
|
+
#s.add_runtime_dependency "rest-client"
|
26
|
+
s.add_runtime_dependency "virtus", "~>1.0.0"
|
26
27
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ObservedEvent
|
4
|
+
end
|
5
|
+
|
6
|
+
class UnobservedEvent
|
7
|
+
end
|
8
|
+
|
9
|
+
class ObserverTest
|
10
|
+
include Replay::Observer
|
11
|
+
observe ObservedEvent do |stream, event|
|
12
|
+
@observed = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.observed?
|
16
|
+
@observed
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.reset
|
20
|
+
@observed=false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe Replay::Observer do
|
25
|
+
before do
|
26
|
+
ObserverTest.reset
|
27
|
+
end
|
28
|
+
it "calls the observer block for observed events" do
|
29
|
+
ObserverTest.published('123', ObservedEvent.new)
|
30
|
+
ObserverTest.must_be :observed?
|
31
|
+
end
|
32
|
+
|
33
|
+
it "does not notify of unobserved events" do
|
34
|
+
ObserverTest.published('123', UnobservedEvent.new)
|
35
|
+
ObserverTest.wont_be :observed?
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Router
|
4
|
+
module Test
|
5
|
+
def has_observer?(object)
|
6
|
+
@subscription_manager.has_subscriber?(object)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
class TypedEvent
|
11
|
+
|
12
|
+
end
|
13
|
+
class Observer
|
14
|
+
def self.published(stream, event)
|
15
|
+
@typed=true
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.typed_received?
|
19
|
+
@typed
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Replay::Router::DefaultRouter.send(:include, Router::Test)
|
24
|
+
|
25
|
+
describe Replay::Router::DefaultRouter do
|
26
|
+
before do
|
27
|
+
@router = Replay::Router::DefaultRouter.instance
|
28
|
+
end
|
29
|
+
describe "adding observers" do
|
30
|
+
it "tracks the observing object" do
|
31
|
+
@router.add_observer(Observer)
|
32
|
+
assert(@router.has_observer?(Observer), "router does not track observer")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
describe "event publishing" do
|
36
|
+
it "tells the observing object about events being published" do
|
37
|
+
@router.add_observer(Observer)
|
38
|
+
@router.published("123", TypedEvent.new)
|
39
|
+
assert Observer.typed_received?, "Did not receive notification of event"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|