stoplight 3.0.0 → 4.0.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/README.md +176 -198
- data/lib/stoplight/builder.rb +68 -0
- data/lib/stoplight/circuit_breaker.rb +92 -0
- data/lib/stoplight/configurable.rb +95 -0
- data/lib/stoplight/configuration.rb +126 -0
- data/lib/stoplight/data_store/base.rb +9 -0
- data/lib/stoplight/data_store/memory.rb +46 -5
- data/lib/stoplight/data_store/redis.rb +75 -6
- data/lib/stoplight/default.rb +2 -0
- data/lib/stoplight/error.rb +1 -0
- data/lib/stoplight/light/deprecated.rb +44 -0
- data/lib/stoplight/light/lockable.rb +45 -0
- data/lib/stoplight/light/runnable.rb +34 -16
- data/lib/stoplight/light.rb +69 -63
- data/lib/stoplight/rspec/generic_notifier.rb +42 -0
- data/lib/stoplight/rspec.rb +3 -0
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight.rb +33 -10
- data/spec/spec_helper.rb +7 -0
- data/spec/stoplight/builder_spec.rb +165 -0
- data/spec/stoplight/circuit_breaker_spec.rb +35 -0
- data/spec/stoplight/configurable_spec.rb +25 -0
- data/spec/stoplight/data_store/base_spec.rb +7 -0
- data/spec/stoplight/data_store/memory_spec.rb +12 -123
- data/spec/stoplight/data_store/redis_spec.rb +28 -129
- data/spec/stoplight/error_spec.rb +10 -0
- data/spec/stoplight/light/lockable_spec.rb +93 -0
- data/spec/stoplight/light/runnable_spec.rb +12 -233
- data/spec/stoplight/light_spec.rb +4 -28
- data/spec/stoplight/notifier/generic_spec.rb +35 -35
- data/spec/stoplight/notifier/io_spec.rb +1 -0
- data/spec/stoplight/notifier/logger_spec.rb +3 -0
- data/spec/stoplight_spec.rb +17 -6
- data/spec/support/configurable.rb +69 -0
- data/spec/support/data_store/base/clear_failures.rb +18 -0
- data/spec/support/data_store/base/clear_state.rb +20 -0
- data/spec/support/data_store/base/get_all.rb +44 -0
- data/spec/support/data_store/base/get_failures.rb +30 -0
- data/spec/support/data_store/base/get_state.rb +7 -0
- data/spec/support/data_store/base/names.rb +29 -0
- data/spec/support/data_store/base/record_failures.rb +70 -0
- data/spec/support/data_store/base/set_state.rb +15 -0
- data/spec/support/data_store/base/with_notification_lock.rb +27 -0
- data/spec/support/data_store/base.rb +21 -0
- data/spec/support/database_cleaner.rb +26 -0
- data/spec/support/exception_helpers.rb +9 -0
- data/spec/support/light/runnable/color.rb +79 -0
- data/spec/support/light/runnable/run.rb +247 -0
- data/spec/support/light/runnable.rb +4 -0
- metadata +56 -225
- data/lib/stoplight/notifier/bugsnag.rb +0 -37
- data/lib/stoplight/notifier/hip_chat.rb +0 -43
- data/lib/stoplight/notifier/honeybadger.rb +0 -44
- data/lib/stoplight/notifier/pagerduty.rb +0 -21
- data/lib/stoplight/notifier/raven.rb +0 -40
- data/lib/stoplight/notifier/rollbar.rb +0 -39
- data/lib/stoplight/notifier/slack.rb +0 -21
- data/spec/stoplight/notifier/bugsnag_spec.rb +0 -90
- data/spec/stoplight/notifier/hip_chat_spec.rb +0 -91
- data/spec/stoplight/notifier/honeybadger_spec.rb +0 -88
- data/spec/stoplight/notifier/pagerduty_spec.rb +0 -40
- data/spec/stoplight/notifier/raven_spec.rb +0 -90
- data/spec/stoplight/notifier/rollbar_spec.rb +0 -90
- data/spec/stoplight/notifier/slack_spec.rb +0 -46
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stoplight
|
4
|
+
class Light
|
5
|
+
# The Lockable module implements the behavior of locking and unlocking the light.
|
6
|
+
# Light can be locked in either a State::LOCKED_RED or State::LOCKED_GREEN state.
|
7
|
+
# By locking the light, you force it always to run code with the chosen light color.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# light = Stoplight('example-locked') { true }
|
11
|
+
# # => #<Stoplight::Light:..>
|
12
|
+
# light.run
|
13
|
+
# # => true
|
14
|
+
# light.lock(Stoplight::Color::RED)
|
15
|
+
# # => #<Stoplight::Light:..>
|
16
|
+
# light.run
|
17
|
+
# # => Stoplight::Error::RedLight: example-locked
|
18
|
+
# light.unlock
|
19
|
+
# # => #<Stoplight::Light:..>
|
20
|
+
# light.run
|
21
|
+
# # => true
|
22
|
+
module Lockable
|
23
|
+
# @param color [String] should be either Color::RED or Color::GREEN
|
24
|
+
# @return [Stoplight::Light] returns locked light
|
25
|
+
def lock(color)
|
26
|
+
state = case color
|
27
|
+
when Color::RED then State::LOCKED_RED
|
28
|
+
when Color::GREEN then State::LOCKED_GREEN
|
29
|
+
else raise Error::IncorrectColor
|
30
|
+
end
|
31
|
+
|
32
|
+
safely { data_store.set_state(self, state) }
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Stoplight::Light] returns unlocked light
|
38
|
+
def unlock
|
39
|
+
safely { data_store.set_state(self, Stoplight::State::UNLOCKED) }
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -13,33 +13,51 @@ module Stoplight
|
|
13
13
|
elsif failures.size < threshold then Color::GREEN
|
14
14
|
elsif failure && Time.now - failure.time >= cool_off_time
|
15
15
|
Color::YELLOW
|
16
|
-
else
|
16
|
+
else
|
17
|
+
Color::RED
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
# @raise [Error::RedLight]
|
21
|
-
def run
|
22
|
+
def run(&code)
|
23
|
+
code = validate_code(&code)
|
22
24
|
case color
|
23
|
-
when Color::GREEN then run_green
|
24
|
-
when Color::YELLOW then run_yellow
|
25
|
+
when Color::GREEN then run_green(&code)
|
26
|
+
when Color::YELLOW then run_yellow(&code)
|
25
27
|
else run_red
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
31
|
private
|
30
32
|
|
31
|
-
def
|
33
|
+
def validate_code(&code)
|
34
|
+
raise ArgumentError, <<~ERROR if block_given? && self.code
|
35
|
+
passing code block into both `Light.new` and `Light#run` is not allowed
|
36
|
+
ERROR
|
37
|
+
|
38
|
+
raise ArgumentError, <<~ERROR unless block_given? || self.code
|
39
|
+
nothing to run. Please, pass a block into `Light#run`
|
40
|
+
ERROR
|
41
|
+
|
42
|
+
code || self.code
|
43
|
+
end
|
44
|
+
|
45
|
+
def run_green(&code)
|
32
46
|
on_failure = lambda do |size, error|
|
33
|
-
notify(Color::GREEN, Color::RED, error) if size
|
47
|
+
notify(Color::GREEN, Color::RED, error) if failures_threshold_breached?(size, threshold)
|
34
48
|
end
|
35
|
-
run_code(nil, on_failure)
|
49
|
+
run_code(nil, on_failure, &code)
|
36
50
|
end
|
37
51
|
|
38
|
-
def
|
52
|
+
def failures_threshold_breached?(current_failures_count, max_errors_threshold)
|
53
|
+
current_failures_count == max_errors_threshold
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_yellow(&code)
|
39
57
|
on_success = lambda do |failures|
|
40
58
|
notify(Color::RED, Color::GREEN) unless failures.empty?
|
41
59
|
end
|
42
|
-
run_code(on_success, nil)
|
60
|
+
run_code(on_success, nil, &code)
|
43
61
|
end
|
44
62
|
|
45
63
|
def run_red
|
@@ -48,7 +66,7 @@ module Stoplight
|
|
48
66
|
fallback.call(nil)
|
49
67
|
end
|
50
68
|
|
51
|
-
def run_code(on_success, on_failure)
|
69
|
+
def run_code(on_success, on_failure, &code)
|
52
70
|
result = code.call
|
53
71
|
failures = clear_failures
|
54
72
|
on_success&.call(failures)
|
@@ -75,8 +93,10 @@ module Stoplight
|
|
75
93
|
end
|
76
94
|
|
77
95
|
def notify(from_color, to_color, error = nil)
|
78
|
-
|
79
|
-
|
96
|
+
data_store.with_notification_lock(self, from_color, to_color) do
|
97
|
+
notifiers.each do |notifier|
|
98
|
+
safely { notifier.notify(self, from_color, to_color, error) }
|
99
|
+
end
|
80
100
|
end
|
81
101
|
end
|
82
102
|
|
@@ -88,15 +108,13 @@ module Stoplight
|
|
88
108
|
def safely(default = nil, &code)
|
89
109
|
return yield if data_store == Default::DATA_STORE
|
90
110
|
|
91
|
-
|
92
|
-
.class
|
93
|
-
.new("#{name}-safely", &code)
|
111
|
+
Stoplight("#{name}-safely")
|
94
112
|
.with_data_store(Default::DATA_STORE)
|
95
113
|
.with_fallback do |error|
|
96
114
|
error_notifier.call(error) if error
|
97
115
|
default
|
98
116
|
end
|
99
|
-
.run
|
117
|
+
.run(&code)
|
100
118
|
end
|
101
119
|
end
|
102
120
|
end
|
data/lib/stoplight/light.rb
CHANGED
@@ -1,103 +1,109 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'stoplight/light/deprecated'
|
4
|
+
|
3
5
|
module Stoplight
|
4
|
-
|
6
|
+
#
|
7
|
+
# @api private use +Stoplight()+ method instead
|
8
|
+
class Light
|
9
|
+
extend Forwardable
|
10
|
+
extend Deprecated
|
11
|
+
include CircuitBreaker
|
12
|
+
include Lockable
|
5
13
|
include Runnable
|
6
14
|
|
15
|
+
# @!attribute [r] data_store
|
16
|
+
# @return [Stoplight::DataStore::Base]
|
17
|
+
def_delegator :configuration, :data_store
|
18
|
+
|
19
|
+
# @!attribute [r] threshold
|
20
|
+
# @return [Integer]
|
21
|
+
def_delegator :configuration, :threshold
|
22
|
+
|
23
|
+
# @!attribute [r] cool_off_time
|
24
|
+
# @return [Fixnum]
|
25
|
+
def_delegator :configuration, :cool_off_time
|
26
|
+
|
27
|
+
# @!attribute [r] window_size
|
28
|
+
# @return [Float]
|
29
|
+
def_delegator :configuration, :window_size
|
30
|
+
|
31
|
+
# @!attribute [r] notifiers
|
32
|
+
# # @return [Array<Notifier::Base>]
|
33
|
+
def_delegator :configuration, :notifiers
|
34
|
+
|
35
|
+
# @!attribute [r] error_notifier
|
36
|
+
# # @return [Proc]
|
37
|
+
def_delegator :configuration, :error_notifier
|
38
|
+
|
39
|
+
# @return [String]
|
40
|
+
attr_reader :name
|
7
41
|
# @return [Proc]
|
8
42
|
attr_reader :code
|
9
|
-
# @return [Float]
|
10
|
-
attr_reader :cool_off_time
|
11
|
-
# @return [DataStore::Base]
|
12
|
-
attr_reader :data_store
|
13
43
|
# @return [Proc]
|
14
44
|
attr_reader :error_handler
|
15
|
-
# @return [Proc]
|
16
|
-
attr_reader :error_notifier
|
17
45
|
# @return [Proc, nil]
|
18
46
|
attr_reader :fallback
|
19
|
-
# @return [
|
20
|
-
|
21
|
-
|
22
|
-
attr_reader :notifiers
|
23
|
-
# @return [Fixnum]
|
24
|
-
attr_reader :threshold
|
47
|
+
# @return [Stoplight::Configuration]
|
48
|
+
# @api private
|
49
|
+
attr_reader :configuration
|
25
50
|
|
26
51
|
class << self
|
27
|
-
|
28
|
-
attr_accessor :default_data_store
|
29
|
-
# @return [Proc]
|
30
|
-
attr_accessor :default_error_notifier
|
31
|
-
# @return [Array<Notifier::Base>]
|
32
|
-
attr_accessor :default_notifiers
|
33
|
-
end
|
52
|
+
alias __new_with_configuration__ new
|
34
53
|
|
35
|
-
|
36
|
-
|
37
|
-
|
54
|
+
# It overrides the +Light.new+ method to support an old and a new
|
55
|
+
# way of instantiation.
|
56
|
+
#
|
57
|
+
# @overload new(name, &code)
|
58
|
+
# @param name [String]
|
59
|
+
# @return [Stoplight::Light]
|
60
|
+
#
|
61
|
+
# @overload new(name, configuration)
|
62
|
+
# @param name [String]
|
63
|
+
# @param configuration [Stoplight::Configuration]
|
64
|
+
# @return [Stoplight::Light]
|
65
|
+
#
|
66
|
+
def new(name, configuration = nil, &code)
|
67
|
+
if configuration
|
68
|
+
__new_with_configuration__(name, configuration, &code)
|
69
|
+
else
|
70
|
+
warn '[DEPRECATED] Instantiating `Stoplight::Light` is deprecated. ' \
|
71
|
+
'Please use `Stoplight()` method instead.'
|
72
|
+
Builder.with(name: name).build(&code)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
38
76
|
|
39
77
|
# @param name [String]
|
78
|
+
# @param configuration [Stoplight::Configuration]
|
40
79
|
# @yield []
|
41
|
-
def initialize(name, &code)
|
80
|
+
def initialize(name, configuration, &code)
|
81
|
+
@configuration = configuration
|
42
82
|
@name = name
|
43
83
|
@code = code
|
44
|
-
|
45
|
-
@cool_off_time = Default::COOL_OFF_TIME
|
46
|
-
@data_store = self.class.default_data_store
|
47
84
|
@error_handler = Default::ERROR_HANDLER
|
48
|
-
@error_notifier = self.class.default_error_notifier
|
49
85
|
@fallback = Default::FALLBACK
|
50
|
-
@notifiers = self.class.default_notifiers
|
51
|
-
@threshold = Default::THRESHOLD
|
52
|
-
end
|
53
|
-
|
54
|
-
# @param cool_off_time [Float]
|
55
|
-
# @return [self]
|
56
|
-
def with_cool_off_time(cool_off_time)
|
57
|
-
@cool_off_time = cool_off_time
|
58
|
-
self
|
59
|
-
end
|
60
|
-
|
61
|
-
# @param data_store [DataStore::Base]
|
62
|
-
# @return [self]
|
63
|
-
def with_data_store(data_store)
|
64
|
-
@data_store = data_store
|
65
|
-
self
|
66
86
|
end
|
67
87
|
|
68
88
|
# @yieldparam error [Exception]
|
69
89
|
# @yieldparam handle [Proc]
|
70
|
-
# @return [
|
90
|
+
# @return [Stoplight::CircuitBreaker]
|
71
91
|
def with_error_handler(&error_handler)
|
72
92
|
@error_handler = error_handler
|
73
93
|
self
|
74
94
|
end
|
75
95
|
|
76
|
-
# @yieldparam error [Exception]
|
77
|
-
# @return [self]
|
78
|
-
def with_error_notifier(&error_notifier)
|
79
|
-
@error_notifier = error_notifier
|
80
|
-
self
|
81
|
-
end
|
82
|
-
|
83
96
|
# @yieldparam error [Exception, nil]
|
84
|
-
# @return [
|
97
|
+
# @return [Stoplight::CircuitBreaker]
|
85
98
|
def with_fallback(&fallback)
|
86
99
|
@fallback = fallback
|
87
100
|
self
|
88
101
|
end
|
89
102
|
|
90
|
-
|
91
|
-
# @return [self]
|
92
|
-
def with_notifiers(notifiers)
|
93
|
-
@notifiers = notifiers
|
94
|
-
self
|
95
|
-
end
|
103
|
+
private
|
96
104
|
|
97
|
-
|
98
|
-
|
99
|
-
def with_threshold(threshold)
|
100
|
-
@threshold = threshold
|
105
|
+
def reconfigure(configuration)
|
106
|
+
@configuration = configuration
|
101
107
|
self
|
102
108
|
end
|
103
109
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'a generic notifier' do
|
4
|
+
it 'includes Generic' do
|
5
|
+
expect(described_class).to include(Stoplight::Notifier::Generic)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#formatter' do
|
9
|
+
it 'is initially the default' do
|
10
|
+
formatter = nil
|
11
|
+
expect(described_class.new(nil, formatter).formatter)
|
12
|
+
.to eql(Stoplight::Default::FORMATTER)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'reads the formatter' do
|
16
|
+
formatter = proc {}
|
17
|
+
expect(described_class.new(nil, formatter).formatter)
|
18
|
+
.to eql(formatter)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#notify' do
|
23
|
+
let(:light) { Stoplight::Light.new(name, &code) }
|
24
|
+
let(:name) { ('a'..'z').to_a.shuffle.join }
|
25
|
+
let(:code) { -> {} }
|
26
|
+
let(:from_color) { Stoplight::Color::GREEN }
|
27
|
+
let(:to_color) { Stoplight::Color::RED }
|
28
|
+
let(:notifier) { described_class.new(double.as_null_object) }
|
29
|
+
|
30
|
+
it 'returns the message' do
|
31
|
+
error = nil
|
32
|
+
expect(notifier.notify(light, from_color, to_color, error))
|
33
|
+
.to eql(notifier.formatter.call(light, from_color, to_color, error))
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns the message with an error' do
|
37
|
+
error = ZeroDivisionError.new('divided by 0')
|
38
|
+
expect(notifier.notify(light, from_color, to_color, error))
|
39
|
+
.to eql(notifier.formatter.call(light, from_color, to_color, error))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/stoplight/version.rb
CHANGED
data/lib/stoplight.rb
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Stoplight # rubocop:disable Style/Documentation
|
4
|
+
class << self
|
5
|
+
# @!attribute default_data_store
|
6
|
+
# @return [DataStore::Base]
|
7
|
+
attr_accessor :default_data_store
|
8
|
+
|
9
|
+
# @!attribute default_notifiers
|
10
|
+
# @return [Array<Notifier::Base>]
|
11
|
+
attr_accessor :default_notifiers
|
12
|
+
|
13
|
+
# @!attribute default_error_notifier
|
14
|
+
# @return [Proc]
|
15
|
+
attr_accessor :default_error_notifier
|
16
|
+
end
|
4
17
|
end
|
5
18
|
|
6
19
|
require 'stoplight/version'
|
@@ -19,22 +32,32 @@ require 'stoplight/notifier'
|
|
19
32
|
require 'stoplight/notifier/base'
|
20
33
|
require 'stoplight/notifier/generic'
|
21
34
|
|
22
|
-
require 'stoplight/notifier/bugsnag'
|
23
|
-
require 'stoplight/notifier/hip_chat'
|
24
|
-
require 'stoplight/notifier/honeybadger'
|
25
35
|
require 'stoplight/notifier/io'
|
26
36
|
require 'stoplight/notifier/logger'
|
27
|
-
require 'stoplight/notifier/pagerduty'
|
28
|
-
require 'stoplight/notifier/raven'
|
29
|
-
require 'stoplight/notifier/rollbar'
|
30
|
-
require 'stoplight/notifier/slack'
|
31
37
|
|
32
38
|
require 'stoplight/default'
|
33
39
|
|
40
|
+
module Stoplight # rubocop:disable Style/Documentation
|
41
|
+
@default_data_store = Default::DATA_STORE
|
42
|
+
@default_notifiers = Default::NOTIFIERS
|
43
|
+
@default_error_notifier = Default::ERROR_NOTIFIER
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'stoplight/configurable'
|
47
|
+
require 'stoplight/circuit_breaker'
|
48
|
+
require 'stoplight/builder'
|
49
|
+
require 'stoplight/configuration'
|
50
|
+
require 'stoplight/light/lockable'
|
34
51
|
require 'stoplight/light/runnable'
|
35
52
|
require 'stoplight/light'
|
36
53
|
|
37
|
-
# @
|
38
|
-
def Stoplight(name, &code) # rubocop:disable
|
39
|
-
|
54
|
+
# @return [Stoplight::CircuitBreaker]
|
55
|
+
def Stoplight(name, &code) # rubocop:disable Naming/MethodName
|
56
|
+
if block_given?
|
57
|
+
warn '[DEPRECATED] Calling `Stoplight("name") { ... }` with a code block is deprecated. ' \
|
58
|
+
'Please pass code block to the run method `Stoplight("name").run { ... }` method instead.'
|
59
|
+
Stoplight::Builder.with(name: name).build(&code)
|
60
|
+
else
|
61
|
+
Stoplight::Builder.with(name: name)
|
62
|
+
end
|
40
63
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,10 +4,17 @@ require 'simplecov'
|
|
4
4
|
|
5
5
|
require 'stoplight'
|
6
6
|
require 'timecop'
|
7
|
+
require_relative 'support/data_store/base'
|
8
|
+
require_relative 'support/light/runnable'
|
9
|
+
require_relative 'support/configurable'
|
10
|
+
require_relative 'support/database_cleaner'
|
11
|
+
require_relative 'support/exception_helpers'
|
7
12
|
|
8
13
|
Timecop.safe_mode = true
|
9
14
|
|
10
15
|
RSpec.configure do |rspec|
|
16
|
+
rspec.include ExceptionHelpers
|
17
|
+
|
11
18
|
rspec.color = true
|
12
19
|
rspec.disable_monkey_patching!
|
13
20
|
rspec.order = :random
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
RSpec.describe Stoplight::Builder do
|
7
|
+
let(:name) { SecureRandom.uuid }
|
8
|
+
|
9
|
+
describe '.with' do
|
10
|
+
context 'with only name' do
|
11
|
+
subject(:configuration) { builder.configuration }
|
12
|
+
|
13
|
+
let(:builder) { described_class.with(name: name) }
|
14
|
+
|
15
|
+
it 'sets configuration to default values' do
|
16
|
+
expect(configuration).to have_attributes(
|
17
|
+
name: name,
|
18
|
+
data_store: Stoplight.default_data_store,
|
19
|
+
notifiers: Stoplight.default_notifiers,
|
20
|
+
cool_off_time: Stoplight::Default::COOL_OFF_TIME,
|
21
|
+
threshold: Stoplight::Default::THRESHOLD,
|
22
|
+
window_size: Stoplight::Default::WINDOW_SIZE
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with configured parameters' do
|
28
|
+
subject(:configuration) { builder.configuration }
|
29
|
+
|
30
|
+
let(:builder) { described_class.with(**configured_parameters) }
|
31
|
+
let(:configured_parameters) do
|
32
|
+
{
|
33
|
+
name: name,
|
34
|
+
data_store: 42,
|
35
|
+
notifiers: [43],
|
36
|
+
cool_off_time: 44,
|
37
|
+
threshold: 45,
|
38
|
+
window_size: 46
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'sets configuration parameters' do
|
43
|
+
expect(configuration).to have_attributes(**configured_parameters)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.build' do
|
49
|
+
let(:builder) { described_class.new(configuration) }
|
50
|
+
let(:configuration) { instance_double(Stoplight::Configuration, name: name) }
|
51
|
+
|
52
|
+
context 'with code' do
|
53
|
+
subject(:light) { builder.build(&code) }
|
54
|
+
|
55
|
+
let(:code) { -> { 42 } }
|
56
|
+
|
57
|
+
it 'builds new light' do
|
58
|
+
expect(light.configuration).to eq(configuration)
|
59
|
+
expect(light.code).to eq(code)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'without code' do
|
64
|
+
subject(:light) { builder.build }
|
65
|
+
|
66
|
+
it 'builds new light' do
|
67
|
+
expect(light.configuration).to eq(configuration)
|
68
|
+
expect(light.code).to eq(nil)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'methods building an instance of light' do
|
74
|
+
let(:builder) { described_class.new(configuration) }
|
75
|
+
let(:configuration) do
|
76
|
+
Stoplight::Configuration.new(
|
77
|
+
name: name,
|
78
|
+
data_store: Stoplight.default_data_store,
|
79
|
+
notifiers: Stoplight.default_notifiers,
|
80
|
+
cool_off_time: Stoplight::Default::COOL_OFF_TIME,
|
81
|
+
threshold: Stoplight::Default::THRESHOLD,
|
82
|
+
window_size: Stoplight::Default::WINDOW_SIZE,
|
83
|
+
error_notifier: Stoplight.default_error_notifier
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '#with_error_handler' do
|
88
|
+
subject(:light) { builder.with_error_handler(&error_handler) }
|
89
|
+
|
90
|
+
let(:error_handler) { ->(error, handle) {} }
|
91
|
+
|
92
|
+
it 'returns an instance of the Light class with this configuration set' do
|
93
|
+
expect(light.configuration).to be(configuration)
|
94
|
+
expect(light.error_handler).to eq(error_handler)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#with_fallback' do
|
99
|
+
subject(:light) { builder.with_fallback(&fallback) }
|
100
|
+
|
101
|
+
let(:fallback) { ->(error) {} }
|
102
|
+
|
103
|
+
it 'returns an instance of the Light class with this configuration set' do
|
104
|
+
expect(light.configuration).to be(configuration)
|
105
|
+
expect(light.fallback).to eq(fallback)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#run' do
|
110
|
+
it 'yields the block' do
|
111
|
+
expect do |code|
|
112
|
+
builder.run(&code)
|
113
|
+
end.to yield_control
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#lock' do
|
118
|
+
context 'when the light is not locked' do
|
119
|
+
it 'locks the light' do
|
120
|
+
expect { builder.lock(Stoplight::Color::RED) }
|
121
|
+
.to change(builder, :color)
|
122
|
+
.from(Stoplight::Color::GREEN)
|
123
|
+
.to(Stoplight::Color::RED)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'when the light is locked' do
|
128
|
+
before do
|
129
|
+
builder.lock(Stoplight::Color::RED)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'does not change the light' do
|
133
|
+
expect { builder.lock(Stoplight::Color::RED) }
|
134
|
+
.not_to change(builder, :color)
|
135
|
+
.from(Stoplight::Color::RED)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#unlock' do
|
141
|
+
context 'when the light is not locked' do
|
142
|
+
it 'does nothing' do
|
143
|
+
expect { builder.unlock }
|
144
|
+
.not_to change(builder, :color)
|
145
|
+
.from(Stoplight::Color::GREEN)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when the light is locked' do
|
150
|
+
before do
|
151
|
+
builder.lock(Stoplight::Color::RED)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'unlocks the light' do
|
155
|
+
expect { builder.unlock }
|
156
|
+
.to change(builder, :color)
|
157
|
+
.from(Stoplight::Color::RED)
|
158
|
+
.to(Stoplight::Color::GREEN)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
it_behaves_like Stoplight::Configurable
|
165
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Stoplight::CircuitBreaker do
|
6
|
+
subject(:circuit_breaker) { klass.new }
|
7
|
+
|
8
|
+
let(:klass) do
|
9
|
+
Class.new { include Stoplight::CircuitBreaker }
|
10
|
+
end
|
11
|
+
|
12
|
+
specify '#with_error_handler' do
|
13
|
+
expect { circuit_breaker.with_error_handler {} }.to raise_error(NotImplementedError)
|
14
|
+
end
|
15
|
+
|
16
|
+
specify '#with_fallback' do
|
17
|
+
expect { circuit_breaker.with_fallback {} }.to raise_error(NotImplementedError)
|
18
|
+
end
|
19
|
+
|
20
|
+
specify '#color' do
|
21
|
+
expect { circuit_breaker.color }.to raise_error(NotImplementedError)
|
22
|
+
end
|
23
|
+
|
24
|
+
specify '#run' do
|
25
|
+
expect { circuit_breaker.run {} }.to raise_error(NotImplementedError)
|
26
|
+
end
|
27
|
+
|
28
|
+
specify '#lock' do
|
29
|
+
expect { circuit_breaker.lock('red') }.to raise_error(NotImplementedError)
|
30
|
+
end
|
31
|
+
|
32
|
+
specify '#unlock' do
|
33
|
+
expect { circuit_breaker.unlock }.to raise_error(NotImplementedError)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Stoplight::Configurable do
|
6
|
+
describe '#with' do
|
7
|
+
subject(:configurable) { configurable_class.new }
|
8
|
+
|
9
|
+
let(:configurable_class) do
|
10
|
+
Class.new do
|
11
|
+
include Stoplight::Configurable
|
12
|
+
|
13
|
+
def configuration
|
14
|
+
Stoplight::Configuration.new(name: 'foo')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'raises NotImplementedError' do
|
20
|
+
expect do
|
21
|
+
configurable.with_data_store(nil)
|
22
|
+
end.to raise_error(NotImplementedError)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -61,4 +61,11 @@ RSpec.describe Stoplight::DataStore::Base do
|
|
61
61
|
.to raise_error(NotImplementedError)
|
62
62
|
end
|
63
63
|
end
|
64
|
+
|
65
|
+
describe '#with_notification_lock' do
|
66
|
+
it 'is not implemented' do
|
67
|
+
expect { data_store.with_notification_lock(nil, nil, nil) }
|
68
|
+
.to raise_error(NotImplementedError)
|
69
|
+
end
|
70
|
+
end
|
64
71
|
end
|