startback 0.4.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/startback/audit/trailer.rb +145 -0
- data/lib/startback/audit.rb +1 -0
- data/lib/startback/bus/bunny/async.rb +117 -0
- data/lib/startback/bus/bunny.rb +1 -0
- data/lib/startback/bus/memory/async.rb +40 -0
- data/lib/startback/bus/memory/sync.rb +30 -0
- data/lib/startback/bus/memory.rb +2 -0
- data/lib/startback/bus.rb +94 -0
- data/lib/startback/caching/entity_cache.rb +80 -0
- data/lib/startback/caching/store.rb +34 -0
- data/lib/startback/context/middleware.rb +1 -1
- data/lib/startback/context.rb +93 -4
- data/lib/startback/event.rb +43 -0
- data/lib/startback/operation.rb +39 -6
- data/lib/startback/support/fake_logger.rb +18 -0
- data/lib/startback/support/hooks.rb +48 -0
- data/lib/startback/support/operation_runner.rb +150 -0
- data/lib/startback/support/robustness.rb +153 -0
- data/lib/startback/support.rb +3 -0
- data/lib/startback/version.rb +2 -2
- data/lib/startback/web/api.rb +3 -4
- data/lib/startback/web/catch_all.rb +12 -5
- data/lib/startback/web/middleware.rb +13 -0
- data/lib/startback.rb +2 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/audit/test_trailer.rb +88 -0
- data/spec/unit/bus/memory/test_async.rb +41 -0
- data/spec/unit/bus/memory/test_sync.rb +41 -0
- data/spec/unit/caching/test_entity_cache.rb +109 -0
- data/spec/unit/context/test_abstraction_factory.rb +64 -0
- data/spec/unit/support/hooks/test_after_hook.rb +54 -0
- data/spec/unit/support/hooks/test_before_hook.rb +54 -0
- data/spec/unit/support/operation_runner/test_around_run.rb +157 -0
- data/spec/unit/support/operation_runner/test_before_after_call.rb +48 -0
- data/spec/unit/support/test_robusteness.rb +209 -0
- data/spec/unit/test_context.rb +51 -0
- data/spec/unit/test_event.rb +69 -0
- data/spec/unit/test_operation.rb +0 -3
- metadata +32 -4
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Startback
|
3
|
+
describe Bus::Memory do
|
4
|
+
|
5
|
+
subject{
|
6
|
+
Bus::Memory::Async.new
|
7
|
+
}
|
8
|
+
|
9
|
+
it 'allows emiting an receiving' do
|
10
|
+
seen = nil
|
11
|
+
subject.listen("user_changed") do |evt|
|
12
|
+
seen = evt
|
13
|
+
end
|
14
|
+
subject.emit(Event.new("user_changed", {id: 12}))
|
15
|
+
expect(seen).to be_a(Event)
|
16
|
+
expect(seen.type).to eql("user_changed")
|
17
|
+
expect(seen.data.to_h).to eql({id: 12})
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'allows mixin Symbol vs. String for event type' do
|
21
|
+
seen = nil
|
22
|
+
subject.listen(:user_changed) do |evt|
|
23
|
+
seen = evt
|
24
|
+
end
|
25
|
+
subject.emit(Event.new(:user_changed, {id: 12}))
|
26
|
+
expect(seen).to be_a(Event)
|
27
|
+
expect(seen.type).to eql("user_changed")
|
28
|
+
expect(seen.data.to_h).to eql({id: 12})
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not raise errors synchronously' do
|
32
|
+
subject.listen("user_changed") do |evt|
|
33
|
+
raise "An error occured"
|
34
|
+
end
|
35
|
+
expect {
|
36
|
+
subject.emit(Event.new("user_changed", {id: 12}))
|
37
|
+
}.not_to raise_error
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Startback
|
3
|
+
describe Bus::Memory do
|
4
|
+
|
5
|
+
subject{
|
6
|
+
Bus::Memory::Sync.new
|
7
|
+
}
|
8
|
+
|
9
|
+
it 'allows emiting an receiving' do
|
10
|
+
seen = nil
|
11
|
+
subject.listen("user_changed") do |evt|
|
12
|
+
seen = evt
|
13
|
+
end
|
14
|
+
subject.emit(Event.new("user_changed", {id: 12}))
|
15
|
+
expect(seen).to be_a(Event)
|
16
|
+
expect(seen.type).to eql("user_changed")
|
17
|
+
expect(seen.data.to_h).to eql({id: 12})
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'allows mixin Symbol vs. String for event type' do
|
21
|
+
seen = nil
|
22
|
+
subject.listen(:user_changed) do |evt|
|
23
|
+
seen = evt
|
24
|
+
end
|
25
|
+
subject.emit(Event.new(:user_changed, {id: 12}))
|
26
|
+
expect(seen).to be_a(Event)
|
27
|
+
expect(seen.type).to eql("user_changed")
|
28
|
+
expect(seen.data.to_h).to eql({id: 12})
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'raises emit errors synchronously' do
|
32
|
+
subject.listen("user_changed") do |evt|
|
33
|
+
raise "An error occured"
|
34
|
+
end
|
35
|
+
expect {
|
36
|
+
subject.emit(Event.new("user_changed", {id: 12}))
|
37
|
+
}.to raise_error("An error occured")
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startback/caching/entity_cache'
|
3
|
+
require 'startback/caching/store'
|
4
|
+
module Startback
|
5
|
+
module Caching
|
6
|
+
describe EntityCache do
|
7
|
+
|
8
|
+
class BaseCache < EntityCache
|
9
|
+
|
10
|
+
def initialize(context = nil)
|
11
|
+
super(Store.new, context)
|
12
|
+
@called = 0
|
13
|
+
@last_key = nil
|
14
|
+
end
|
15
|
+
attr_reader :called, :last_key
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def full_key(key)
|
20
|
+
{ k: key }
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_raw_data(key)
|
24
|
+
@called += 1
|
25
|
+
@last_key = key
|
26
|
+
"a value"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class ShortCache < BaseCache
|
32
|
+
self.default_ttl = 1
|
33
|
+
end
|
34
|
+
|
35
|
+
class InvalidatingCache < BaseCache
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def valid?(key, value)
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:cache) {
|
46
|
+
BaseCache.new
|
47
|
+
}
|
48
|
+
|
49
|
+
describe "default_ttl" do
|
50
|
+
|
51
|
+
it 'has a default ttl of one hour' do
|
52
|
+
expect(BaseCache.default_ttl).to eql(3600)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'allows overriding it' do
|
56
|
+
expect(ShortCache.default_ttl).to eql(1)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'is accessible as default_caching_options on the instance' do
|
60
|
+
expect(cache.send(:default_caching_options)).to eql({ttl: 3600})
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "get" do
|
66
|
+
|
67
|
+
subject{
|
68
|
+
cache.get("a key")
|
69
|
+
}
|
70
|
+
|
71
|
+
it 'yields to load_raw_data only once with the short key' do
|
72
|
+
expect(subject).to eql("a value")
|
73
|
+
expect(subject).to eql("a value")
|
74
|
+
expect(cache.called).to eql(1)
|
75
|
+
expect(cache.last_key).to eql("a key")
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "invalidate" do
|
81
|
+
|
82
|
+
it 'strips the key on the store, yielding a cache miss' do
|
83
|
+
expect(cache.get("a key")).to eql("a value")
|
84
|
+
cache.invalidate("a key")
|
85
|
+
expect(cache.get("a key")).to eql("a value")
|
86
|
+
expect(cache.called).to eql(2)
|
87
|
+
expect(cache.last_key).to eql("a key")
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "valid? override" do
|
93
|
+
|
94
|
+
let(:cache) {
|
95
|
+
InvalidatingCache.new
|
96
|
+
}
|
97
|
+
|
98
|
+
it 'yields to load_raw_data only once with the extend key' do
|
99
|
+
expect(cache.get("a key")).to eql("a value")
|
100
|
+
expect(cache.get("a key")).to eql("a value")
|
101
|
+
expect(cache.called).to eql(2)
|
102
|
+
expect(cache.last_key).to eql("a key")
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Startback
|
4
|
+
describe Context do
|
5
|
+
|
6
|
+
let(:context) {
|
7
|
+
Context.new
|
8
|
+
}
|
9
|
+
|
10
|
+
class ContextRelatedAbstraction
|
11
|
+
|
12
|
+
def initialize(context)
|
13
|
+
@context = context
|
14
|
+
end
|
15
|
+
attr_reader :context
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class ContextRelatedAbstractionWithArgs
|
20
|
+
|
21
|
+
def initialize(arg1, arg2, context)
|
22
|
+
@arg1 = arg1
|
23
|
+
@arg2 = arg2
|
24
|
+
@context = context
|
25
|
+
end
|
26
|
+
attr_reader :arg1, :arg2, :context
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'is a factory for other context-related abstractions' do
|
31
|
+
got = context.factor(ContextRelatedAbstraction)
|
32
|
+
expect(got).to be_a(ContextRelatedAbstraction)
|
33
|
+
expect(got.context).to be(context)
|
34
|
+
|
35
|
+
got2 = context.factor(ContextRelatedAbstraction)
|
36
|
+
expect(got2).to be(got)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'is takes cares of abstraction arguments' do
|
40
|
+
got = context.factor(ContextRelatedAbstractionWithArgs, 12, 14)
|
41
|
+
expect(got).to be_a(ContextRelatedAbstractionWithArgs)
|
42
|
+
expect(got.context).to be(context)
|
43
|
+
expect(got.arg1).to eql(12)
|
44
|
+
expect(got.arg2).to eql(14)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'is caches even in presence ofabstraction arguments' do
|
48
|
+
got = context.factor(ContextRelatedAbstractionWithArgs, 12, 14)
|
49
|
+
expect(got).to be_a(ContextRelatedAbstractionWithArgs)
|
50
|
+
|
51
|
+
got2 = context.factor(ContextRelatedAbstractionWithArgs, 12, 14)
|
52
|
+
expect(got2).to be(got)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'is distinguishes different abstraction arguments' do
|
56
|
+
got = context.factor(ContextRelatedAbstractionWithArgs, 12, 14)
|
57
|
+
expect(got).to be_a(ContextRelatedAbstractionWithArgs)
|
58
|
+
|
59
|
+
got2 = context.factor(ContextRelatedAbstractionWithArgs, 17, 14)
|
60
|
+
expect(got2).not_to be(got)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'singleton'
|
3
|
+
module Startback
|
4
|
+
module Support
|
5
|
+
describe Hooks, "after_xxx" do
|
6
|
+
|
7
|
+
class AfterHooked
|
8
|
+
include Hooks.new(:xxx)
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
@after_called = false
|
13
|
+
end
|
14
|
+
attr_accessor :after_called
|
15
|
+
|
16
|
+
after_xxx do
|
17
|
+
self.after_called = true
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class SubAfterHooked < AfterHooked
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
super
|
26
|
+
@subafter_called = false
|
27
|
+
end
|
28
|
+
attr_accessor :subafter_called
|
29
|
+
|
30
|
+
after_xxx do
|
31
|
+
self.subafter_called = true
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'works as expected' do
|
37
|
+
h = AfterHooked.new
|
38
|
+
expect(h.after_called).to eql(false)
|
39
|
+
h.after_xxx
|
40
|
+
expect(h.after_called).to eql(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'works as expected on subclass' do
|
44
|
+
h = SubAfterHooked.new
|
45
|
+
expect(h.after_called).to eql(false)
|
46
|
+
expect(h.subafter_called).to eql(false)
|
47
|
+
h.after_xxx
|
48
|
+
expect(h.after_called).to eql(true)
|
49
|
+
expect(h.subafter_called).to eql(true)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'singleton'
|
3
|
+
module Startback
|
4
|
+
module Support
|
5
|
+
describe Hooks, "before_xxx" do
|
6
|
+
|
7
|
+
class BeforeHooked
|
8
|
+
include Hooks.new(:xxx)
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
@before_called = false
|
13
|
+
end
|
14
|
+
attr_accessor :before_called
|
15
|
+
|
16
|
+
before_xxx do
|
17
|
+
self.before_called = true
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class SubBeforeHooked < BeforeHooked
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
super
|
26
|
+
@subbefore_called = false
|
27
|
+
end
|
28
|
+
attr_accessor :subbefore_called
|
29
|
+
|
30
|
+
before_xxx do
|
31
|
+
self.subbefore_called = true
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'works as expected' do
|
37
|
+
h = BeforeHooked.new
|
38
|
+
expect(h.before_called).to eql(false)
|
39
|
+
h.before_xxx
|
40
|
+
expect(h.before_called).to eql(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'works as expected on subclass' do
|
44
|
+
h = SubBeforeHooked.new
|
45
|
+
expect(h.before_called).to eql(false)
|
46
|
+
expect(h.subbefore_called).to eql(false)
|
47
|
+
h.before_xxx
|
48
|
+
expect(h.before_called).to eql(true)
|
49
|
+
expect(h.subbefore_called).to eql(true)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'singleton'
|
3
|
+
module Startback
|
4
|
+
module Support
|
5
|
+
describe OperationRunner, "around_run" do
|
6
|
+
|
7
|
+
class OperationTest < Startback::Operation
|
8
|
+
|
9
|
+
def call
|
10
|
+
{ seen_hello: hello }
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:op) {
|
16
|
+
OperationTest.new
|
17
|
+
}
|
18
|
+
|
19
|
+
context 'the simplest contract' do
|
20
|
+
class RunnerTest1
|
21
|
+
include OperationRunner
|
22
|
+
|
23
|
+
def operation_world(op)
|
24
|
+
{ hello: "world"}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'lets run an operation with world bound' do
|
29
|
+
expect(RunnerTest1.new.run(op)).to eql({
|
30
|
+
seen_hello: "world"
|
31
|
+
})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'the around feature' do
|
36
|
+
class RunnerTest2
|
37
|
+
include OperationRunner
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@arounds = []
|
41
|
+
end
|
42
|
+
attr_reader :arounds
|
43
|
+
|
44
|
+
around_run do |o, then_block|
|
45
|
+
raise unless o.is_a?(OperationTest)
|
46
|
+
arounds << "hello"
|
47
|
+
then_block.call
|
48
|
+
end
|
49
|
+
|
50
|
+
around_run do |_, then_block|
|
51
|
+
arounds << "world"
|
52
|
+
then_block.call
|
53
|
+
end
|
54
|
+
|
55
|
+
def operation_world(op)
|
56
|
+
{ hello: "world" }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'calls the around before the operation itself' do
|
61
|
+
test = RunnerTest2.new
|
62
|
+
got = test.run(op)
|
63
|
+
expect(test.arounds).to eql(["hello", "world"])
|
64
|
+
expect(got).to eql({
|
65
|
+
seen_hello: "world"
|
66
|
+
})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'the around feature with a class' do
|
71
|
+
class TransactionManager
|
72
|
+
include Singleton
|
73
|
+
|
74
|
+
def initialize
|
75
|
+
@called = false
|
76
|
+
end
|
77
|
+
attr_reader :called
|
78
|
+
|
79
|
+
def call(runner, op)
|
80
|
+
raise unless runner.is_a?(RunnerTest3)
|
81
|
+
raise unless op.is_a?(OperationTest)
|
82
|
+
@called = true
|
83
|
+
yield
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class RunnerTest3
|
89
|
+
include OperationRunner
|
90
|
+
around_run TransactionManager.instance
|
91
|
+
|
92
|
+
def operation_world(op)
|
93
|
+
{ hello: "world" }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'calls the proc with expected parameters' do
|
98
|
+
test = RunnerTest3.new
|
99
|
+
got = test.run(op)
|
100
|
+
expect(TransactionManager.instance.called).to eql(true)
|
101
|
+
expect(got).to eql({
|
102
|
+
seen_hello: "world"
|
103
|
+
})
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'the around feature with a subclass' do
|
108
|
+
class RunnerTest4
|
109
|
+
include OperationRunner
|
110
|
+
|
111
|
+
def initialize
|
112
|
+
@called = false
|
113
|
+
end
|
114
|
+
attr_reader :called
|
115
|
+
|
116
|
+
around_run do |o,t|
|
117
|
+
raise unless o.is_a?(OperationTest)
|
118
|
+
@called = true
|
119
|
+
t.call
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class RunnerTest5 < RunnerTest4
|
124
|
+
|
125
|
+
def initialize
|
126
|
+
super
|
127
|
+
@subcalled = false
|
128
|
+
end
|
129
|
+
attr_reader :subcalled
|
130
|
+
|
131
|
+
around_run do |o,t|
|
132
|
+
raise unless o.is_a?(OperationTest)
|
133
|
+
@subcalled = true
|
134
|
+
t.call
|
135
|
+
end
|
136
|
+
|
137
|
+
def operation_world(op)
|
138
|
+
{ hello: "world" }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'executes all hooks' do
|
143
|
+
test = RunnerTest5.new
|
144
|
+
got = test.run(op)
|
145
|
+
expect(test.called).to be(true)
|
146
|
+
expect(test.subcalled).to be(true)
|
147
|
+
expect(got).to eql({
|
148
|
+
seen_hello: "world"
|
149
|
+
})
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end # module OperationRunner
|
155
|
+
end # module Support
|
156
|
+
end # module Startback
|
157
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'singleton'
|
3
|
+
module Startback
|
4
|
+
module Support
|
5
|
+
describe OperationRunner, "before_call" do
|
6
|
+
|
7
|
+
class OperationTestBeforeCall < Operation
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
before_called = false
|
11
|
+
end
|
12
|
+
attr_accessor :before_called
|
13
|
+
|
14
|
+
before_call do
|
15
|
+
self.before_called = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
{
|
20
|
+
seen_hello: "world"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:op) {
|
27
|
+
OperationTestBeforeCall.new
|
28
|
+
}
|
29
|
+
|
30
|
+
class RunnerTest1
|
31
|
+
include OperationRunner
|
32
|
+
|
33
|
+
def operation_world(op)
|
34
|
+
{ hello: "world" }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'runs before the around hooks' do
|
39
|
+
expect(RunnerTest1.new.run(op)).to eql({
|
40
|
+
seen_hello: "world"
|
41
|
+
})
|
42
|
+
expect(op.before_called).to eql(true)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|