status_lib 0.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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/bin/guard +16 -0
- data/lib/status_lib/cache.rb +33 -0
- data/lib/status_lib/config.rb +61 -0
- data/lib/status_lib/null_object.rb +6 -0
- data/lib/status_lib/null_stats_handler.rb +3 -0
- data/lib/status_lib/status_api.rb +62 -0
- data/lib/status_lib/status_info.rb +72 -0
- data/lib/status_lib/version.rb +3 -0
- data/lib/status_lib.rb +119 -0
- data/spec/lib/status_lib/cache_spec.rb +45 -0
- data/spec/lib/status_lib/config_spec.rb +100 -0
- data/spec/lib/status_lib/null_object_spec.rb +28 -0
- data/spec/lib/status_lib/null_stats_handler_spec.rb +9 -0
- data/spec/lib/status_lib/status_api_spec.rb +122 -0
- data/spec/lib/status_lib/status_info_spec.rb +146 -0
- data/spec/lib/status_lib_spec.rb +195 -0
- data/spec/spec_helper.rb +17 -0
- data/status_lib.gemspec +30 -0
- metadata +231 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p484
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kevin McConnell
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# StatusLib
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'status_lib'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install status_lib
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/guard
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'guard' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('guard', 'guard')
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class StatusLib::Cache
|
2
|
+
class << self
|
3
|
+
def fetch(item, options={})
|
4
|
+
expires_in = options[:expires_in]
|
5
|
+
stored = items[item]
|
6
|
+
if stored.nil? || expired?(expires_in, timestamps[item])
|
7
|
+
stored = items[item] = yield
|
8
|
+
timestamps[item] = Time.now
|
9
|
+
end
|
10
|
+
stored
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear!
|
14
|
+
@timestamps = @cached_items = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def items
|
20
|
+
@cached_items ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def timestamps
|
24
|
+
@timestamps ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def expired?(duration, recorded_time)
|
28
|
+
return false if duration.nil?
|
29
|
+
return true if recorded_time.nil?
|
30
|
+
(Time.now - recorded_time) > duration
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class StatusLib::Config
|
2
|
+
DEFAULT_CACHE_TIME = 5
|
3
|
+
DEFAULT_API_CALL_TIMEOUT = 1
|
4
|
+
DEFAULT_DOWN_DURATION = 0
|
5
|
+
DEFAULT_BLOCK_TIMEOUT = 5
|
6
|
+
|
7
|
+
ATTRIBUTES = [:server,
|
8
|
+
:exception_handler, :stats_handler,
|
9
|
+
:cache_time, :api_call_timeout,
|
10
|
+
:down_duration, :block_timeout]
|
11
|
+
|
12
|
+
attr_reader *ATTRIBUTES
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
reset_to_defaults
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(args)
|
19
|
+
ATTRIBUTES.each do |arg|
|
20
|
+
self.instance_variable_set("@#{arg}", args[arg] || defaults[arg]) if args.include?(arg)
|
21
|
+
end
|
22
|
+
clear_memoized
|
23
|
+
end
|
24
|
+
|
25
|
+
def status_info
|
26
|
+
@status_info ||= StatusLib::StatusInfo.new(status_api)
|
27
|
+
end
|
28
|
+
|
29
|
+
def status_api
|
30
|
+
@status_api ||= status_api_for_config
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def reset_to_defaults
|
36
|
+
update(Hash[ATTRIBUTES.map { |attr| [attr, nil] }])
|
37
|
+
end
|
38
|
+
|
39
|
+
def defaults
|
40
|
+
{
|
41
|
+
cache_time: DEFAULT_CACHE_TIME,
|
42
|
+
api_call_timeout: DEFAULT_API_CALL_TIMEOUT,
|
43
|
+
down_duration: DEFAULT_DOWN_DURATION,
|
44
|
+
block_timeout: DEFAULT_BLOCK_TIMEOUT,
|
45
|
+
stats_handler: NullStatsHandler
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear_memoized
|
50
|
+
@status_info = nil
|
51
|
+
@status_api = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def status_api_for_config
|
55
|
+
if server.nil?
|
56
|
+
StatusLib::NullStatusApi.new
|
57
|
+
else
|
58
|
+
StatusLib::StatusApi.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'timeout'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
class StatusLib::ApiDownError < Exception; end
|
7
|
+
|
8
|
+
class StatusLib::NullStatusApi
|
9
|
+
def get_status_list
|
10
|
+
end
|
11
|
+
|
12
|
+
def send_status_update(name, status, expires)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class StatusLib::StatusApi
|
17
|
+
def get_status_list
|
18
|
+
response = send_request('GET', "/api/status")
|
19
|
+
parse_status_response(response.body)
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_status_update(name, status, expires)
|
23
|
+
if expires.kind_of?(Time)
|
24
|
+
expires = expires.utc.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
payload = { status: status, expires: expires }.to_json
|
28
|
+
|
29
|
+
response = send_request('PUT', "/api/status/#{name}", payload)
|
30
|
+
response_successful(response)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parse_status_response(body)
|
36
|
+
Hash[
|
37
|
+
JSON.parse(body).map { |name, status| [name.to_sym, status.to_sym ] }
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
def http_request
|
42
|
+
uri = URI.parse(StatusLib.config.server)
|
43
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
44
|
+
end
|
45
|
+
|
46
|
+
def send_request(*args)
|
47
|
+
Timeout::timeout(StatusLib.config.api_call_timeout) do
|
48
|
+
response = http_request.send_request(*args)
|
49
|
+
raise ArgumentError.new("service not found") if response.code == "404"
|
50
|
+
raise StatusLib::ApiDownError unless response_successful(response)
|
51
|
+
response
|
52
|
+
end
|
53
|
+
rescue ArgumentError
|
54
|
+
raise
|
55
|
+
rescue
|
56
|
+
raise StatusLib::ApiDownError
|
57
|
+
end
|
58
|
+
|
59
|
+
def response_successful(response)
|
60
|
+
response.kind_of? Net::HTTPSuccess
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class StatusLib::StatusInfo
|
2
|
+
def initialize(api)
|
3
|
+
@api = api
|
4
|
+
@statuses = {}
|
5
|
+
@expiries = {}
|
6
|
+
@updates = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def up?(name)
|
10
|
+
sync_status_list
|
11
|
+
status = @statuses[name]
|
12
|
+
expiry = @expiries[name]
|
13
|
+
|
14
|
+
if status == :down && (expiry.nil? || expiry > now)
|
15
|
+
return false
|
16
|
+
else
|
17
|
+
return true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def switch(name, status, options={})
|
22
|
+
unless [:up, :down].include?(status)
|
23
|
+
raise ArgumentError.new("unknown status")
|
24
|
+
end
|
25
|
+
|
26
|
+
expires = options.fetch(:for, StatusLib.config.down_duration)
|
27
|
+
expires = now + expires unless expires.nil?
|
28
|
+
|
29
|
+
@statuses[name] = status
|
30
|
+
@expiries[name] = expires
|
31
|
+
|
32
|
+
add_pending_update(name, status, expires)
|
33
|
+
send_pending_updates
|
34
|
+
|
35
|
+
StatusLib.config.stats_handler.increment("status.change.#{name}.#{status}")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def now
|
41
|
+
Time.now
|
42
|
+
end
|
43
|
+
|
44
|
+
def sync_status_list
|
45
|
+
expires_in = StatusLib.config.cache_time
|
46
|
+
|
47
|
+
@statuses = StatusLib::Cache.fetch('statuses', expires_in: expires_in) do
|
48
|
+
send_pending_updates
|
49
|
+
begin
|
50
|
+
@api.get_status_list || @statuses
|
51
|
+
rescue StatusLib::ApiDownError
|
52
|
+
# couldn't get the info; just use our local copy
|
53
|
+
@statuses
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_pending_update(name, status, expires)
|
59
|
+
@updates.delete(name)
|
60
|
+
@updates[name] = [status, expires]
|
61
|
+
end
|
62
|
+
|
63
|
+
def send_pending_updates
|
64
|
+
@updates.delete_if do |name, args|
|
65
|
+
begin
|
66
|
+
@api.send_status_update(name, *args)
|
67
|
+
rescue StatusLib::ApiDownError
|
68
|
+
false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/status_lib.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'status_lib/version'
|
2
|
+
|
3
|
+
require 'status_lib/cache'
|
4
|
+
require 'status_lib/config'
|
5
|
+
require 'status_lib/null_object'
|
6
|
+
require 'status_lib/null_stats_handler'
|
7
|
+
require 'status_lib/status_api'
|
8
|
+
require 'status_lib/status_info'
|
9
|
+
|
10
|
+
require 'timeout'
|
11
|
+
|
12
|
+
module StatusLib
|
13
|
+
class ServiceDownError < StandardError
|
14
|
+
attr_reader :original_exception
|
15
|
+
|
16
|
+
def initialize(original_exception = nil)
|
17
|
+
super("Service is down")
|
18
|
+
@original_exception = original_exception
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_reader :server
|
24
|
+
|
25
|
+
# Checks whether a particular service is up or down.
|
26
|
+
#
|
27
|
+
# Params:
|
28
|
+
# +name+:: the name of the service to check
|
29
|
+
#
|
30
|
+
def up?(name)
|
31
|
+
status_info.up?(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the status of a service to up or down
|
35
|
+
#
|
36
|
+
# Params:
|
37
|
+
# +name+:: the name of the service to update
|
38
|
+
# +status+:: the desired status; either :up or :down
|
39
|
+
# +options+:: additional options. can contain:
|
40
|
+
# +:for+:: the time to keep the service down, in seconds
|
41
|
+
#
|
42
|
+
def switch(name, status, options={})
|
43
|
+
status_info.switch(name, status, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Helper method for guarding a block of code with a curcuit breaker.
|
47
|
+
#
|
48
|
+
# If the code in the block raises an exception or exceeds its timeout, then
|
49
|
+
# the associated service status will be set to down for the specified
|
50
|
+
# interval, and a StatusLib::ServiceDownError exception will be raised.
|
51
|
+
#
|
52
|
+
# If the service is already makred down then the block won't be evaluated at
|
53
|
+
# all, and StatusLib::ServiceDownError will be raised immediately.
|
54
|
+
#
|
55
|
+
# +name+:: the name of the service to update
|
56
|
+
# +options+:: additional options. can contain:
|
57
|
+
# +:timeout+:: upper limit on how long the block can take to run
|
58
|
+
# +:down_for+:: the time to keep the service down, in seconds
|
59
|
+
#
|
60
|
+
def with_circuit_breaker(name, options={})
|
61
|
+
raise ServiceDownError unless up?(name)
|
62
|
+
|
63
|
+
Timeout::timeout( determine_timeout(options) ) do
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
rescue Timeout::Error => e
|
67
|
+
handle_exception(e, name, options)
|
68
|
+
raise e
|
69
|
+
rescue => e
|
70
|
+
handle_exception(e, name, options)
|
71
|
+
raise ServiceDownError.new(e)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Configure the gem
|
75
|
+
#
|
76
|
+
# Params:
|
77
|
+
# +settings+:: the configuration settings. A hash that may contain:
|
78
|
+
# +:server+:: the URL of the status page service
|
79
|
+
# +:cache_time+:: time to cache status reponses from the API
|
80
|
+
# +:api_call_timeout+:: maximum time to wait for a call to status API
|
81
|
+
# +:down_duration+:: default time to switch a service down for
|
82
|
+
# +:block_timeout+:: default time that a `with_circuit_breaker` can take
|
83
|
+
#
|
84
|
+
def configure(settings)
|
85
|
+
config.update(settings)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Read configuration settings
|
89
|
+
#
|
90
|
+
# The values that are set to #configure are available here.
|
91
|
+
#
|
92
|
+
def config
|
93
|
+
@config ||= Config.new
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def handle_exception(e, name, options)
|
99
|
+
report_exception(e)
|
100
|
+
switch(name, :down, options) if has_down_for?(options)
|
101
|
+
end
|
102
|
+
|
103
|
+
def status_info
|
104
|
+
config.status_info
|
105
|
+
end
|
106
|
+
|
107
|
+
def determine_timeout(options)
|
108
|
+
options.delete(:timeout) || config.block_timeout
|
109
|
+
end
|
110
|
+
|
111
|
+
def has_down_for?(options)
|
112
|
+
(options[:for] || 0) > 0
|
113
|
+
end
|
114
|
+
|
115
|
+
def report_exception(e)
|
116
|
+
config.exception_handler.call(e) unless config.exception_handler.nil?
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StatusLib::Cache do
|
4
|
+
let(:target) { double(:target, message: "test") }
|
5
|
+
|
6
|
+
before do
|
7
|
+
StatusLib::Cache.clear!
|
8
|
+
end
|
9
|
+
|
10
|
+
context "#fetch" do
|
11
|
+
context "with no prior data" do
|
12
|
+
it "fetches the value" do
|
13
|
+
value = StatusLib::Cache.fetch('key') { target.message }
|
14
|
+
|
15
|
+
expect(value).to eq('test')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when cached" do
|
20
|
+
it "does not evalute the block multiple times" do
|
21
|
+
value = StatusLib::Cache.fetch('key') { target.message }
|
22
|
+
value = StatusLib::Cache.fetch('key') { target.message }
|
23
|
+
|
24
|
+
expect(value).to eq('test')
|
25
|
+
expect(target).to have_received(:message).once
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when cached for a specific amount of time" do
|
30
|
+
it "only evaluates the block after the timeout elapses" do
|
31
|
+
Timecop.freeze(0)
|
32
|
+
StatusLib::Cache.fetch('key', expires_in: 30) { target.message }
|
33
|
+
expect(target).to have_received(:message).once
|
34
|
+
|
35
|
+
Timecop.freeze(15)
|
36
|
+
StatusLib::Cache.fetch('key', expires_in: 30) { target.message }
|
37
|
+
expect(target).to have_received(:message).once
|
38
|
+
|
39
|
+
Timecop.freeze(60)
|
40
|
+
StatusLib::Cache.fetch('key', expires_in: 30) { target.message }
|
41
|
+
expect(target).to have_received(:message).twice
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StatusLib::Config do
|
4
|
+
shared_examples_for "it has default values" do
|
5
|
+
it "has no default server address" do
|
6
|
+
expect(config.server).to be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "uses a NullStatusApi by default" do
|
10
|
+
expect(config.status_api).to be_an_instance_of(StatusLib::NullStatusApi)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has no default exception handler" do
|
14
|
+
expect(config.exception_handler).to be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has a null stats handler by default" do
|
18
|
+
expect(config.stats_handler).to eq(NullStatsHandler)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has a default cache expiry time" do
|
22
|
+
expect(config.cache_time).to eq(5)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "has a default API call timeout" do
|
26
|
+
expect(config.api_call_timeout).to eq(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "has a default down duration" do
|
30
|
+
expect(config.down_duration).to eq(0)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "has a default block timeout" do
|
34
|
+
expect(config.block_timeout).to eq(5)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when initially created" do
|
39
|
+
let(:config) { StatusLib::Config.new }
|
40
|
+
|
41
|
+
it_behaves_like "it has default values"
|
42
|
+
end
|
43
|
+
|
44
|
+
context "updating" do
|
45
|
+
let(:config) { StatusLib::Config.new }
|
46
|
+
let(:stats) { double(:stats) }
|
47
|
+
|
48
|
+
it "sets the values form the arguments" do
|
49
|
+
config.update(server: "http://example.com:123",
|
50
|
+
exception_handler: ->(e) { puts e },
|
51
|
+
stats_handler: stats,
|
52
|
+
cache_time: 60,
|
53
|
+
api_call_timeout: 0.5,
|
54
|
+
down_duration: 120,
|
55
|
+
block_timeout: 36)
|
56
|
+
|
57
|
+
expect(config.server).to eq("http://example.com:123")
|
58
|
+
expect(config.exception_handler).to be_kind_of(Proc)
|
59
|
+
expect(config.stats_handler).to eq(stats)
|
60
|
+
expect(config.cache_time).to eq(60)
|
61
|
+
expect(config.api_call_timeout).to eq(0.5)
|
62
|
+
expect(config.down_duration).to eq(120)
|
63
|
+
expect(config.block_timeout).to eq(36)
|
64
|
+
expect(config.status_api).to be_an_instance_of(StatusLib::StatusApi)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "setting back to nil" do
|
69
|
+
let(:config) { StatusLib::Config.new }
|
70
|
+
let(:stats) { double(:stats) }
|
71
|
+
|
72
|
+
before do
|
73
|
+
config.update(server: "http://example.com:123",
|
74
|
+
exception_handler: ->(e) { puts e },
|
75
|
+
stats_handler: stats,
|
76
|
+
cache_time: 60,
|
77
|
+
api_call_timeout: 0.5,
|
78
|
+
down_duration: 120,
|
79
|
+
block_timeout: 36)
|
80
|
+
|
81
|
+
config.update(server: nil,
|
82
|
+
exception_handler: nil,
|
83
|
+
stats_handler: nil,
|
84
|
+
cache_time: nil,
|
85
|
+
api_call_timeout: nil,
|
86
|
+
down_duration: nil,
|
87
|
+
block_timeout: nil)
|
88
|
+
end
|
89
|
+
|
90
|
+
it_behaves_like "it has default values"
|
91
|
+
end
|
92
|
+
|
93
|
+
context "#status_info" do
|
94
|
+
let(:config) { StatusLib::Config.new }
|
95
|
+
|
96
|
+
it "has an instance of StatusInfo" do
|
97
|
+
expect(config.status_info).to be_an_instance_of(StatusLib::StatusInfo)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe NullObject do
|
4
|
+
let(:klass) {
|
5
|
+
Class.new do
|
6
|
+
include NullObject
|
7
|
+
end
|
8
|
+
}
|
9
|
+
let(:instance) { klass.new }
|
10
|
+
|
11
|
+
it "quietly ignores method calls" do
|
12
|
+
expect {
|
13
|
+
instance.do_something_i_just_made_up('with', 'args')
|
14
|
+
}.to_not raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with a block" do
|
18
|
+
let(:inner) { double(:inner).as_null_object }
|
19
|
+
|
20
|
+
it "calls the supplied block on ignored methods" do
|
21
|
+
instance.do_something_with_a_block do
|
22
|
+
inner.block_target('test')
|
23
|
+
end
|
24
|
+
|
25
|
+
expect(inner).to have_received(:block_target).with('test')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|