stoplight 3.0.0 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
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