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.
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