stoplight 3.0.0 → 3.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f7c163955f261ad3eeca89a851a06a15f5734d78d045695297f29c143532a58
4
- data.tar.gz: 9ff3e5911bf7e9f198c714ce1eb0f10c6ba09834e96a8c8c2d16dd3e184ed0ce
3
+ metadata.gz: bbfed26c7b63f56e88cd970d5db820022a89d689570cd4e98168b7226d718791
4
+ data.tar.gz: 9206b2c2a66e78d39f37e7932fc95faaf850a2cc54d024eab2a2d90dc14396b4
5
5
  SHA512:
6
- metadata.gz: 5e42cd0050cdbf9440e80d68d70091e6c22c574c64cf06a29aa9dca320f5f4915dca48e5582ebf18db757fde4ad295c20af05bc6a879695d726afa65ff50dea8
7
- data.tar.gz: a5088d9ba81a5578cbc078d0671c601fd13738f07f6a67dd12936df3e8a7927053314538d50c1e65ce6913607e9764ff09d6c05f931c4be8984e2e6744399288
6
+ metadata.gz: 34d477b3b8e0c6e9b34a0a1ea7e0e6fab4950601d914407063a113f019b9e4a7420b4b291f76702e326b41f3b7cd780adb55f9c2df28bd659c9be7d384af35b4
7
+ data.tar.gz: ab49c8f6cac59358b78807fa82e88e8ee3cec5c668dad2f6cc0ca8183507dc64373f611c23b83dce4d2d5958f176d6c447e796a9137e87a165b0cabd12b7dc7c
data/README.md CHANGED
@@ -28,7 +28,6 @@ Check out [stoplight-admin][] for controlling your stoplights.
28
28
  - [Redis](#redis)
29
29
  - [Notifiers](#notifiers)
30
30
  - [Bugsnag](#bugsnag)
31
- - [HipChat](#hipchat)
32
31
  - [Honeybadger](#honeybadger)
33
32
  - [Logger](#logger)
34
33
  - [Pagerduty](#pagerduty)
@@ -311,22 +310,6 @@ Stoplight::Light.default_notifiers += [notifier]
311
310
  # => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::Bugsnag:...>]
312
311
  ```
313
312
 
314
- #### HipChat
315
-
316
- Make sure you have [the HipChat gem][] (`~> 1.5`) installed before configuring
317
- Stoplight.
318
-
319
- ``` rb
320
- require 'hipchat'
321
- # => true
322
- hip_chat = HipChat::Client.new('token')
323
- # => #<HipChat::Client:...>
324
- notifier = Stoplight::Notifier::HipChat.new(hip_chat, 'room')
325
- # => #<Stoplight::Notifier::HipChat:...>
326
- Stoplight::Light.default_notifiers += [notifier]
327
- # => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::HipChat:...>]
328
- ```
329
-
330
313
  #### Honeybadger
331
314
 
332
315
  Make sure you have [the Honeybadger gem][] (`~> 2.5`) installed before
@@ -516,7 +499,6 @@ Stoplight is licensed under [the MIT License][].
516
499
  [the cool off time section]: #custom-cool-off-time
517
500
  [the Redis gem]: https://rubygems.org/gems/redis
518
501
  [the Bugsnag gem]: https://rubygems.org/gems/bugsnag
519
- [the HipChat gem]: https://rubygems.org/gems/hipchat
520
502
  [the Honeybadger gem]: https://rubygems.org/gems/honeybadger
521
503
  [the Logger class]: http://ruby-doc.org/stdlib-2.2.3/libdoc/logger/rdoc/Logger.html
522
504
  [the Rollbar gem]: https://rubygems.org/gems/rollbar
@@ -52,6 +52,15 @@ module Stoplight
52
52
  def clear_state(_light)
53
53
  raise NotImplementedError
54
54
  end
55
+
56
+ # @param _light [Light]
57
+ # @param _from_color [String]
58
+ # @param _to_color [String]
59
+ # @yield _block
60
+ # @return [Void]
61
+ def with_notification_lock(_light, _from_color, _to_color, &_block)
62
+ raise NotImplementedError
63
+ end
55
64
  end
56
65
  end
57
66
  end
@@ -7,10 +7,12 @@ module Stoplight
7
7
  # @see Base
8
8
  class Memory < Base
9
9
  include MonitorMixin
10
+ KEY_SEPARATOR = ':'
10
11
 
11
12
  def initialize
12
13
  @failures = Hash.new { |h, k| h[k] = [] }
13
14
  @states = Hash.new { |h, k| h[k] = State::UNLOCKED }
15
+ @last_notifications = {}
14
16
  super() # MonitorMixin
15
17
  end
16
18
 
@@ -49,6 +51,30 @@ module Stoplight
49
51
  def clear_state(light)
50
52
  synchronize { @states.delete(light.name) }
51
53
  end
54
+
55
+ def with_notification_lock(light, from_color, to_color)
56
+ synchronize do
57
+ if last_notification(light) != [from_color, to_color]
58
+ set_last_notification(light, from_color, to_color)
59
+
60
+ yield
61
+ end
62
+ end
63
+ end
64
+
65
+ # @param light [Stoplight::Light]
66
+ # @return [Array, nil]
67
+ def last_notification(light)
68
+ @last_notifications[light.name]
69
+ end
70
+
71
+ # @param light [Stoplight::Light]
72
+ # @param from_color [String]
73
+ # @param to_color [String]
74
+ # @return [void]
75
+ def set_last_notification(light, from_color, to_color)
76
+ @last_notifications[light.name] = [from_color, to_color]
77
+ end
52
78
  end
53
79
  end
54
80
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'redlock'
4
+
3
5
  module Stoplight
4
6
  module DataStore
5
7
  # @see Base
@@ -8,8 +10,9 @@ module Stoplight
8
10
  KEY_SEPARATOR = ':'
9
11
 
10
12
  # @param redis [::Redis]
11
- def initialize(redis)
13
+ def initialize(redis, redlock: Redlock::Client.new([redis]))
12
14
  @redis = redis
15
+ @redlock = redlock
13
16
  end
14
17
 
15
18
  def names
@@ -76,8 +79,34 @@ module Stoplight
76
79
  normalize_state(state)
77
80
  end
78
81
 
82
+ LOCK_TTL = 2_000 # milliseconds
83
+
84
+ def with_notification_lock(light, from_color, to_color)
85
+ @redlock.lock(notification_lock_key(light), LOCK_TTL) do
86
+ if last_notification(light) != [from_color, to_color]
87
+ set_last_notification(light, from_color, to_color)
88
+
89
+ yield
90
+ end
91
+ end
92
+ end
93
+
79
94
  private
80
95
 
96
+ # @param light [Stoplight::Light]
97
+ # @return [Array, nil]
98
+ def last_notification(light)
99
+ @redis.get(last_notification_key(light))&.split('->')
100
+ end
101
+
102
+ # @param light [Stoplight::Light]
103
+ # @param from_color [String]
104
+ # @param to_color [String]
105
+ # @return [void]
106
+ def set_last_notification(light, from_color, to_color)
107
+ @redis.set(last_notification_key(light), [from_color, to_color].join('->'))
108
+ end
109
+
81
110
  def query_failures(light, transaction: @redis)
82
111
  transaction.lrange(failures_key(light), 0, -1)
83
112
  end
@@ -103,6 +132,14 @@ module Stoplight
103
132
  key('failures', light.name)
104
133
  end
105
134
 
135
+ def notification_lock_key(light)
136
+ key('notification_lock', light.name)
137
+ end
138
+
139
+ def last_notification_key(light)
140
+ key('last_notification', light.name)
141
+ end
142
+
106
143
  def states_key
107
144
  key('states')
108
145
  end
@@ -30,11 +30,15 @@ module Stoplight
30
30
 
31
31
  def run_green
32
32
  on_failure = lambda do |size, error|
33
- notify(Color::GREEN, Color::RED, error) if size == threshold
33
+ notify(Color::GREEN, Color::RED, error) if failures_threshold_breached?(size, threshold)
34
34
  end
35
35
  run_code(nil, on_failure)
36
36
  end
37
37
 
38
+ def failures_threshold_breached?(current_failures_count, max_errors_threshold)
39
+ current_failures_count == max_errors_threshold
40
+ end
41
+
38
42
  def run_yellow
39
43
  on_success = lambda do |failures|
40
44
  notify(Color::RED, Color::GREEN) unless failures.empty?
@@ -48,7 +52,18 @@ module Stoplight
48
52
  fallback.call(nil)
49
53
  end
50
54
 
55
+ MISSING_BLOCK_ERROR = <<~ERROR
56
+ Oops! An error occurred while executing the `Stoplight#run` method. This happened because you
57
+ didn't pass a code block to the `Stoplight()` function. You can fix this issue this way:
58
+
59
+ Stoplight('test-light') { ... }.run
60
+
61
+ For more details and examples, please refer to the documentation https://github.com/bolshakov/stoplight/tree/release/v3.x
62
+ ERROR
63
+
51
64
  def run_code(on_success, on_failure)
65
+ raise ArgumentError, MISSING_BLOCK_ERROR unless code
66
+
52
67
  result = code.call
53
68
  failures = clear_failures
54
69
  on_success&.call(failures)
@@ -75,8 +90,10 @@ module Stoplight
75
90
  end
76
91
 
77
92
  def notify(from_color, to_color, error = nil)
78
- notifiers.each do |notifier|
79
- safely { notifier.notify(self, from_color, to_color, error) }
93
+ data_store.with_notification_lock(self, from_color, to_color) do
94
+ notifiers.each do |notifier|
95
+ safely { notifier.notify(self, from_color, to_color, error) }
96
+ end
80
97
  end
81
98
  end
82
99
 
@@ -29,7 +29,7 @@ module Stoplight
29
29
 
30
30
  def notify(light, from_color, to_color, error)
31
31
  message = formatter.call(light, from_color, to_color, error)
32
- bugsnag.notify(StoplightStatusChange.new(message), options)
32
+ bugsnag.notify(StoplightStatusChange.new(message), **options)
33
33
  message
34
34
  end
35
35
  end
@@ -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('3.0.2')
5
5
  end
data/lib/stoplight.rb CHANGED
@@ -20,7 +20,6 @@ require 'stoplight/notifier/base'
20
20
  require 'stoplight/notifier/generic'
21
21
 
22
22
  require 'stoplight/notifier/bugsnag'
23
- require 'stoplight/notifier/hip_chat'
24
23
  require 'stoplight/notifier/honeybadger'
25
24
  require 'stoplight/notifier/io'
26
25
  require 'stoplight/notifier/logger'
@@ -35,6 +34,6 @@ require 'stoplight/light/runnable'
35
34
  require 'stoplight/light'
36
35
 
37
36
  # @see Stoplight::Light#initialize
38
- def Stoplight(name, &code) # rubocop:disable Style/MethodName
37
+ def Stoplight(name, &code) # rubocop:disable Naming/MethodName
39
38
  Stoplight::Light.new(name, &code)
40
39
  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
@@ -130,4 +130,30 @@ RSpec.describe Stoplight::DataStore::Memory do
130
130
  expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
131
131
  end
132
132
  end
133
+
134
+ describe '#with_notification_lock' do
135
+ context 'when notification is already sent' do
136
+ before do
137
+ data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
138
+ end
139
+
140
+ it 'does not yield passed block' do
141
+ expect do |b|
142
+ data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED, &b)
143
+ end.not_to yield_control
144
+ end
145
+ end
146
+
147
+ context 'when notification is not already sent' do
148
+ before do
149
+ data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
150
+ end
151
+
152
+ it 'yields passed block' do
153
+ expect do |b|
154
+ data_store.with_notification_lock(light, Stoplight::Color::RED, Stoplight::Color::GREEN, &b)
155
+ end.to yield_control
156
+ end
157
+ end
158
+ end
133
159
  end
@@ -1,17 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
- require 'fakeredis'
4
+ require 'mock_redis'
5
5
 
6
6
  RSpec.describe Stoplight::DataStore::Redis do
7
- let(:data_store) { described_class.new(redis) }
8
- let(:redis) { Redis.new }
7
+ let(:data_store) { described_class.new(redis, redlock: redlock) }
8
+ let(:redis) { MockRedis.new }
9
+ let(:redlock) { instance_double(Redlock::Client) }
9
10
  let(:light) { Stoplight::Light.new(name) {} }
10
11
  let(:name) { ('a'..'z').to_a.shuffle.join }
11
12
  let(:failure) { Stoplight::Failure.new('class', 'message', Time.new) }
12
13
 
13
- before { Redis::Connection::Memory.reset_all_databases }
14
-
15
14
  it 'is a class' do
16
15
  expect(described_class).to be_a(Class)
17
16
  end
@@ -143,4 +142,36 @@ RSpec.describe Stoplight::DataStore::Redis do
143
142
  expect(data_store.get_state(light)).to eql(Stoplight::State::UNLOCKED)
144
143
  end
145
144
  end
145
+
146
+ describe '#with_notification_lock' do
147
+ let(:lock_key) { "stoplight:notification_lock:#{name}" }
148
+
149
+ before do
150
+ allow(redlock).to receive(:lock).with(lock_key, 2_000).and_yield
151
+ end
152
+
153
+ context 'when notification is already sent' do
154
+ before do
155
+ data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
156
+ end
157
+
158
+ it 'does not yield passed block' do
159
+ expect do |b|
160
+ data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED, &b)
161
+ end.not_to yield_control
162
+ end
163
+ end
164
+
165
+ context 'when notification is not already sent' do
166
+ before do
167
+ data_store.with_notification_lock(light, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
168
+ end
169
+
170
+ it 'yields passed block' do
171
+ expect do |b|
172
+ data_store.with_notification_lock(light, Stoplight::Color::RED, Stoplight::Color::GREEN, &b)
173
+ end.to yield_control
174
+ end
175
+ end
176
+ end
146
177
  end
@@ -109,6 +109,38 @@ RSpec.describe Stoplight::Light::Runnable do
109
109
  expect(subject.data_store.get_failures(subject).size).to eql(1)
110
110
  end
111
111
 
112
+ context 'when we did not send notifications yet' do
113
+ it 'notifies when transitioning to red' do
114
+ subject.threshold.times do
115
+ expect(io.string).to eql('')
116
+ begin
117
+ subject.run
118
+ rescue error.class
119
+ nil
120
+ end
121
+ end
122
+ expect(io.string).to_not eql('')
123
+ end
124
+ end
125
+
126
+ context 'when we already sent notifications' do
127
+ before do
128
+ subject.data_store.with_notification_lock(subject, Stoplight::Color::GREEN, Stoplight::Color::RED) {}
129
+ end
130
+
131
+ it 'does not send new notifications' do
132
+ subject.threshold.times do
133
+ expect(io.string).to eql('')
134
+ begin
135
+ subject.run
136
+ rescue error.class
137
+ nil
138
+ end
139
+ end
140
+ expect(io.string).to eql('')
141
+ end
142
+ end
143
+
112
144
  it 'notifies when transitioning to red' do
113
145
  subject.threshold.times do
114
146
  expect(io.string).to eql('')
@@ -253,5 +285,13 @@ RSpec.describe Stoplight::Light::Runnable do
253
285
  end
254
286
  end
255
287
  end
288
+
289
+ context 'when the code block is missing' do
290
+ subject { Stoplight::Light.new(name) }
291
+
292
+ it 'raises an ArgumentError error' do
293
+ expect { subject.run }.to raise_error(ArgumentError)
294
+ end
295
+ end
256
296
  end
257
297
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stoplight
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Desautels
@@ -10,8 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-02-23 00:00:00.000000000 Z
13
+ date: 2023-08-28 00:00:00.000000000 Z
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: redlock
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.0'
15
29
  - !ruby/object:Gem::Dependency
16
30
  name: benchmark-ips
17
31
  requirement: !ruby/object:Gem::Requirement
@@ -46,42 +60,42 @@ dependencies:
46
60
  requirements:
47
61
  - - "~>"
48
62
  - !ruby/object:Gem::Version
49
- version: '0.5'
63
+ version: '0.8'
50
64
  type: :development
51
65
  prerelease: false
52
66
  version_requirements: !ruby/object:Gem::Requirement
53
67
  requirements:
54
68
  - - "~>"
55
69
  - !ruby/object:Gem::Version
56
- version: '0.5'
70
+ version: '0.8'
57
71
  - !ruby/object:Gem::Dependency
58
- name: hipchat
72
+ name: honeybadger
59
73
  requirement: !ruby/object:Gem::Requirement
60
74
  requirements:
61
75
  - - "~>"
62
76
  - !ruby/object:Gem::Version
63
- version: '1.5'
77
+ version: '2.5'
64
78
  type: :development
65
79
  prerelease: false
66
80
  version_requirements: !ruby/object:Gem::Requirement
67
81
  requirements:
68
82
  - - "~>"
69
83
  - !ruby/object:Gem::Version
70
- version: '1.5'
84
+ version: '2.5'
71
85
  - !ruby/object:Gem::Dependency
72
- name: honeybadger
86
+ name: mock_redis
73
87
  requirement: !ruby/object:Gem::Requirement
74
88
  requirements:
75
89
  - - "~>"
76
90
  - !ruby/object:Gem::Version
77
- version: '2.5'
91
+ version: '0.3'
78
92
  type: :development
79
93
  prerelease: false
80
94
  version_requirements: !ruby/object:Gem::Requirement
81
95
  requirements:
82
96
  - - "~>"
83
97
  - !ruby/object:Gem::Version
84
- version: '2.5'
98
+ version: '0.3'
85
99
  - !ruby/object:Gem::Dependency
86
100
  name: pagerduty
87
101
  requirement: !ruby/object:Gem::Requirement
@@ -116,28 +130,28 @@ dependencies:
116
130
  requirements:
117
131
  - - "~>"
118
132
  - !ruby/object:Gem::Version
119
- version: '3.2'
133
+ version: '4.1'
120
134
  type: :development
121
135
  prerelease: false
122
136
  version_requirements: !ruby/object:Gem::Requirement
123
137
  requirements:
124
138
  - - "~>"
125
139
  - !ruby/object:Gem::Version
126
- version: '3.2'
140
+ version: '4.1'
127
141
  - !ruby/object:Gem::Dependency
128
142
  name: rspec
129
143
  requirement: !ruby/object:Gem::Requirement
130
144
  requirements:
131
145
  - - "~>"
132
146
  - !ruby/object:Gem::Version
133
- version: '3.3'
147
+ version: '3.11'
134
148
  type: :development
135
149
  prerelease: false
136
150
  version_requirements: !ruby/object:Gem::Requirement
137
151
  requirements:
138
152
  - - "~>"
139
153
  - !ruby/object:Gem::Version
140
- version: '3.3'
154
+ version: '3.11'
141
155
  - !ruby/object:Gem::Dependency
142
156
  name: rubocop
143
157
  requirement: !ruby/object:Gem::Requirement
@@ -214,14 +228,14 @@ dependencies:
214
228
  requirements:
215
229
  - - "~>"
216
230
  - !ruby/object:Gem::Version
217
- version: '0.8'
231
+ version: '0.9'
218
232
  type: :development
219
233
  prerelease: false
220
234
  version_requirements: !ruby/object:Gem::Requirement
221
235
  requirements:
222
236
  - - "~>"
223
237
  - !ruby/object:Gem::Version
224
- version: '0.8'
238
+ version: '0.9'
225
239
  description: An implementation of the circuit breaker pattern.
226
240
  email:
227
241
  - camdez@gmail.com
@@ -249,7 +263,6 @@ files:
249
263
  - lib/stoplight/notifier/base.rb
250
264
  - lib/stoplight/notifier/bugsnag.rb
251
265
  - lib/stoplight/notifier/generic.rb
252
- - lib/stoplight/notifier/hip_chat.rb
253
266
  - lib/stoplight/notifier/honeybadger.rb
254
267
  - lib/stoplight/notifier/io.rb
255
268
  - lib/stoplight/notifier/logger.rb
@@ -273,7 +286,6 @@ files:
273
286
  - spec/stoplight/notifier/base_spec.rb
274
287
  - spec/stoplight/notifier/bugsnag_spec.rb
275
288
  - spec/stoplight/notifier/generic_spec.rb
276
- - spec/stoplight/notifier/hip_chat_spec.rb
277
289
  - spec/stoplight/notifier/honeybadger_spec.rb
278
290
  - spec/stoplight/notifier/io_spec.rb
279
291
  - spec/stoplight/notifier/logger_spec.rb
@@ -304,7 +316,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
304
316
  - !ruby/object:Gem::Version
305
317
  version: '0'
306
318
  requirements: []
307
- rubygems_version: 3.2.3
319
+ rubygems_version: 3.3.7
308
320
  signing_key:
309
321
  specification_version: 4
310
322
  summary: Traffic control for code.
@@ -323,7 +335,6 @@ test_files:
323
335
  - spec/stoplight/notifier/base_spec.rb
324
336
  - spec/stoplight/notifier/bugsnag_spec.rb
325
337
  - spec/stoplight/notifier/generic_spec.rb
326
- - spec/stoplight/notifier/hip_chat_spec.rb
327
338
  - spec/stoplight/notifier/honeybadger_spec.rb
328
339
  - spec/stoplight/notifier/io_spec.rb
329
340
  - spec/stoplight/notifier/logger_spec.rb
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Notifier
5
- # @see Base
6
- class HipChat < Base
7
- DEFAULT_OPTIONS = {
8
- color: 'purple',
9
- message_format: 'text',
10
- notify: true
11
- }.freeze
12
-
13
- # @return [Proc]
14
- attr_reader :formatter
15
- # @return [::HipChat::Client]
16
- attr_reader :hip_chat
17
- # @return [Hash{Symbol => Object}]
18
- attr_reader :options
19
- # @return [String]
20
- attr_reader :room
21
-
22
- # @param hip_chat [::HipChat::Client]
23
- # @param room [String]
24
- # @param formatter [Proc, nil]
25
- # @param options [Hash{Symbol => Object}]
26
- # @option options [String] :color
27
- # @option options [String] :message_format
28
- # @option options [Boolean] :notify
29
- def initialize(hip_chat, room, formatter = nil, options = {})
30
- @hip_chat = hip_chat
31
- @room = room
32
- @formatter = formatter || Default::FORMATTER
33
- @options = DEFAULT_OPTIONS.merge(options)
34
- end
35
-
36
- def notify(light, from_color, to_color, error)
37
- message = formatter.call(light, from_color, to_color, error)
38
- hip_chat[room].send('Stoplight', message, options)
39
- message
40
- end
41
- end
42
- end
43
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- # require 'hipchat'
6
- module HipChat
7
- class Client
8
- def initialize(*); end
9
- end
10
- end
11
-
12
- RSpec.describe Stoplight::Notifier::HipChat do
13
- it 'is a class' do
14
- expect(described_class).to be_a(Class)
15
- end
16
-
17
- it 'is a subclass of Base' do
18
- expect(described_class).to be < Stoplight::Notifier::Base
19
- end
20
-
21
- describe '#formatter' do
22
- it 'is initially the default' do
23
- expect(described_class.new(nil, nil).formatter)
24
- .to eql(Stoplight::Default::FORMATTER)
25
- end
26
-
27
- it 'reads the formatter' do
28
- formatter = proc {}
29
- expect(described_class.new(nil, nil, formatter).formatter)
30
- .to eql(formatter)
31
- end
32
- end
33
-
34
- describe '#hip_chat' do
35
- it 'reads the HipChat client' do
36
- hip_chat = HipChat::Client.new('API token')
37
- expect(described_class.new(hip_chat, nil).hip_chat)
38
- .to eql(hip_chat)
39
- end
40
- end
41
-
42
- describe '#options' do
43
- it 'is initially the default' do
44
- expect(described_class.new(nil, nil).options)
45
- .to eql(Stoplight::Notifier::HipChat::DEFAULT_OPTIONS)
46
- end
47
-
48
- it 'reads the options' do
49
- options = { key: :value }
50
- expect(described_class.new(nil, nil, nil, options).options)
51
- .to eql(Stoplight::Notifier::HipChat::DEFAULT_OPTIONS.merge(options))
52
- end
53
- end
54
-
55
- describe '#room' do
56
- it 'reads the room' do
57
- room = 'Notifications'
58
- expect(described_class.new(nil, room).room).to eql(room)
59
- end
60
- end
61
-
62
- describe '#notify' do
63
- let(:light) { Stoplight::Light.new(name, &code) }
64
- let(:name) { ('a'..'z').to_a.shuffle.join }
65
- let(:code) { -> {} }
66
- let(:from_color) { Stoplight::Color::GREEN }
67
- let(:to_color) { Stoplight::Color::RED }
68
- let(:notifier) { described_class.new(hip_chat, room) }
69
- let(:hip_chat) { double(HipChat::Client) }
70
- let(:room) { ('a'..'z').to_a.shuffle.join }
71
-
72
- before do
73
- tmp = double
74
- expect(hip_chat).to receive(:[]).with(room).and_return(tmp)
75
- expect(tmp).to receive(:send)
76
- .with('Stoplight', kind_of(String), kind_of(Hash)).and_return(true)
77
- end
78
-
79
- it 'returns the message' do
80
- error = nil
81
- expect(notifier.notify(light, from_color, to_color, error))
82
- .to eql(notifier.formatter.call(light, from_color, to_color, error))
83
- end
84
-
85
- it 'returns the message with an error' do
86
- error = ZeroDivisionError.new('divided by 0')
87
- expect(notifier.notify(light, from_color, to_color, error))
88
- .to eql(notifier.formatter.call(light, from_color, to_color, error))
89
- end
90
- end
91
- end