startback 0.4.5 → 0.5.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 +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
|