stoplight 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +176 -198
  3. data/lib/stoplight/builder.rb +68 -0
  4. data/lib/stoplight/circuit_breaker.rb +92 -0
  5. data/lib/stoplight/configurable.rb +95 -0
  6. data/lib/stoplight/configuration.rb +126 -0
  7. data/lib/stoplight/data_store/base.rb +9 -0
  8. data/lib/stoplight/data_store/memory.rb +46 -5
  9. data/lib/stoplight/data_store/redis.rb +75 -6
  10. data/lib/stoplight/default.rb +2 -0
  11. data/lib/stoplight/error.rb +1 -0
  12. data/lib/stoplight/light/deprecated.rb +44 -0
  13. data/lib/stoplight/light/lockable.rb +45 -0
  14. data/lib/stoplight/light/runnable.rb +34 -16
  15. data/lib/stoplight/light.rb +69 -63
  16. data/lib/stoplight/rspec/generic_notifier.rb +42 -0
  17. data/lib/stoplight/rspec.rb +3 -0
  18. data/lib/stoplight/version.rb +1 -1
  19. data/lib/stoplight.rb +33 -10
  20. data/spec/spec_helper.rb +7 -0
  21. data/spec/stoplight/builder_spec.rb +165 -0
  22. data/spec/stoplight/circuit_breaker_spec.rb +35 -0
  23. data/spec/stoplight/configurable_spec.rb +25 -0
  24. data/spec/stoplight/data_store/base_spec.rb +7 -0
  25. data/spec/stoplight/data_store/memory_spec.rb +12 -123
  26. data/spec/stoplight/data_store/redis_spec.rb +28 -129
  27. data/spec/stoplight/error_spec.rb +10 -0
  28. data/spec/stoplight/light/lockable_spec.rb +93 -0
  29. data/spec/stoplight/light/runnable_spec.rb +12 -233
  30. data/spec/stoplight/light_spec.rb +4 -28
  31. data/spec/stoplight/notifier/generic_spec.rb +35 -35
  32. data/spec/stoplight/notifier/io_spec.rb +1 -0
  33. data/spec/stoplight/notifier/logger_spec.rb +3 -0
  34. data/spec/stoplight_spec.rb +17 -6
  35. data/spec/support/configurable.rb +69 -0
  36. data/spec/support/data_store/base/clear_failures.rb +18 -0
  37. data/spec/support/data_store/base/clear_state.rb +20 -0
  38. data/spec/support/data_store/base/get_all.rb +44 -0
  39. data/spec/support/data_store/base/get_failures.rb +30 -0
  40. data/spec/support/data_store/base/get_state.rb +7 -0
  41. data/spec/support/data_store/base/names.rb +29 -0
  42. data/spec/support/data_store/base/record_failures.rb +70 -0
  43. data/spec/support/data_store/base/set_state.rb +15 -0
  44. data/spec/support/data_store/base/with_notification_lock.rb +27 -0
  45. data/spec/support/data_store/base.rb +21 -0
  46. data/spec/support/database_cleaner.rb +26 -0
  47. data/spec/support/exception_helpers.rb +9 -0
  48. data/spec/support/light/runnable/color.rb +79 -0
  49. data/spec/support/light/runnable/run.rb +247 -0
  50. data/spec/support/light/runnable.rb +4 -0
  51. metadata +56 -225
  52. data/lib/stoplight/notifier/bugsnag.rb +0 -37
  53. data/lib/stoplight/notifier/hip_chat.rb +0 -43
  54. data/lib/stoplight/notifier/honeybadger.rb +0 -44
  55. data/lib/stoplight/notifier/pagerduty.rb +0 -21
  56. data/lib/stoplight/notifier/raven.rb +0 -40
  57. data/lib/stoplight/notifier/rollbar.rb +0 -39
  58. data/lib/stoplight/notifier/slack.rb +0 -21
  59. data/spec/stoplight/notifier/bugsnag_spec.rb +0 -90
  60. data/spec/stoplight/notifier/hip_chat_spec.rb +0 -91
  61. data/spec/stoplight/notifier/honeybadger_spec.rb +0 -88
  62. data/spec/stoplight/notifier/pagerduty_spec.rb +0 -40
  63. data/spec/stoplight/notifier/raven_spec.rb +0 -90
  64. data/spec/stoplight/notifier/rollbar_spec.rb +0 -90
  65. 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 Color::RED
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 run_green
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 == threshold
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 run_yellow
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
- notifiers.each do |notifier|
79
- safely { notifier.notify(self, from_color, to_color, error) }
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
- self
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
@@ -1,103 +1,109 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'stoplight/light/deprecated'
4
+
3
5
  module Stoplight
4
- class Light # rubocop:disable Style/Documentation
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 [String]
20
- attr_reader :name
21
- # @return [Array<Notifier::Base>]
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
- # @return [DataStore::Base]
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
- @default_data_store = Default::DATA_STORE
36
- @default_error_notifier = Default::ERROR_NOTIFIER
37
- @default_notifiers = Default::NOTIFIERS
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 [self]
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 [self]
97
+ # @return [Stoplight::CircuitBreaker]
85
98
  def with_fallback(&fallback)
86
99
  @fallback = fallback
87
100
  self
88
101
  end
89
102
 
90
- # @param notifiers [Array<Notifier::Base>]
91
- # @return [self]
92
- def with_notifiers(notifiers)
93
- @notifiers = notifiers
94
- self
95
- end
103
+ private
96
104
 
97
- # @param threshold [Fixnum]
98
- # @return [self]
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
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stoplight/rspec/generic_notifier'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stoplight
4
- VERSION = Gem::Version.new('3.0.0')
4
+ VERSION = Gem::Version.new('4.0.0')
5
5
  end
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
- # @see Stoplight::Light#initialize
38
- def Stoplight(name, &code) # rubocop:disable Style/MethodName
39
- Stoplight::Light.new(name, &code)
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