spokes 0.1.1
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 +7 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +12 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/README.md +137 -0
- data/Rakefile +1 -0
- data/lib/spokes.rb +13 -0
- data/lib/spokes/config/env.rb +65 -0
- data/lib/spokes/middleware/concerns/bad_request.rb +22 -0
- data/lib/spokes/middleware/concerns/header_validation.rb +23 -0
- data/lib/spokes/middleware/cors.rb +71 -0
- data/lib/spokes/middleware/health.rb +79 -0
- data/lib/spokes/middleware/request_id.rb +43 -0
- data/lib/spokes/middleware/service_name.rb +65 -0
- data/lib/spokes/version.rb +3 -0
- data/lib/spokes/versioning/config/minor_versions.yml +3 -0
- data/lib/spokes/versioning/minor_versioning.rb +71 -0
- data/lib/spokes/versioning/railtie.rb +22 -0
- data/lib/spokes/versioning/tasks/minor_versioning.rake +14 -0
- data/logo/spokes_logo.png +0 -0
- data/script/cibuild +12 -0
- data/script/postbuild +17 -0
- data/script/test +23 -0
- data/spec/config/env_spec.rb +111 -0
- data/spec/middleware/cors_spec.rb +29 -0
- data/spec/middleware/health_spec.rb +107 -0
- data/spec/middleware/request_id_spec.rb +19 -0
- data/spec/middleware/service_name_spec.rb +60 -0
- data/spec/rails_helper.rb +3 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/versioning/minor_versioning_spec.rb +45 -0
- data/spokes.gemspec +29 -0
- metadata +255 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
module Spokes
|
2
|
+
module Middleware
|
3
|
+
class RequestID
|
4
|
+
PATTERN = /^[\w\\-_\\.\d]+$/
|
5
|
+
|
6
|
+
def initialize(app, service_name:)
|
7
|
+
raise "invalid name: #{service_name}" unless service_name =~ PATTERN
|
8
|
+
|
9
|
+
@app = app
|
10
|
+
@service_name = service_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
id = env['action_dispatch.request_id'] || SecureRandom.uuid
|
15
|
+
request_ids = extract_request_ids(env).insert(0, @service_name + ':' + id)
|
16
|
+
|
17
|
+
# make ID of the request accessible to consumers down the stack
|
18
|
+
env['REQUEST_ID'] = request_ids[0]
|
19
|
+
|
20
|
+
# Extract request IDs from incoming headers as well. Can be used for
|
21
|
+
# identifying a request across a number of components in SOA.
|
22
|
+
env['REQUEST_IDS'] = request_ids
|
23
|
+
|
24
|
+
Thread.current[:request_chain] = env['REQUEST_IDS']
|
25
|
+
|
26
|
+
@app.call(env)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def extract_request_ids(env)
|
32
|
+
request_ids = raw_request_ids(env)
|
33
|
+
request_ids.map!(&:strip)
|
34
|
+
request_ids
|
35
|
+
end
|
36
|
+
|
37
|
+
def raw_request_ids(_env)
|
38
|
+
%w[HTTP_REQUEST_CHAIN].each_with_object([]) do |key, _request_ids|
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Spokes
|
2
|
+
module Middleware
|
3
|
+
# Validates inbound and sets outbound Service-Name HTTP headers.
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# class Application < Rails::Application
|
8
|
+
# config.middleware.use Spokes::Middleware::ServiceName
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
class ServiceName
|
12
|
+
include Middleware::Concerns::BadRequest
|
13
|
+
include Middleware::Concerns::HeaderValidation
|
14
|
+
|
15
|
+
PATTERN = /^[\w\\-_\\.\d]+$/
|
16
|
+
HEADER_NAME = 'Service-Name'.freeze
|
17
|
+
|
18
|
+
def initialize(app, service_name:, exclude_paths: [])
|
19
|
+
raise "invalid name: #{service_name}" unless service_name =~ PATTERN
|
20
|
+
@app = app
|
21
|
+
@service_name = service_name
|
22
|
+
@exclude_paths = path_to_regex(exclude_paths)
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(env)
|
26
|
+
begin
|
27
|
+
unless exclude?(env['PATH_INFO'].chomp('/'))
|
28
|
+
validate_header_presence(env: env, header_name: HEADER_NAME)
|
29
|
+
validate_header_pattern(env: env, header_name: HEADER_NAME, pattern: PATTERN)
|
30
|
+
end
|
31
|
+
rescue Middleware::Concerns::HeaderValidation::NotValid => e
|
32
|
+
return bad_request(e.message)
|
33
|
+
end
|
34
|
+
|
35
|
+
status, headers, body = @app.call(env)
|
36
|
+
headers[HEADER_NAME] = @service_name
|
37
|
+
[status, headers, body]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def exclude?(path)
|
43
|
+
@exclude_paths.each do |regex|
|
44
|
+
return true if regex.match?(path)
|
45
|
+
end
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
# build out regular expression to match exclude path
|
50
|
+
def path_to_regex(exclude_paths)
|
51
|
+
reg_ex_path = []
|
52
|
+
exclude_paths.each do |path|
|
53
|
+
path_parts = path.split('/')
|
54
|
+
regex_paths = path_parts.inject do |out, p|
|
55
|
+
val = p
|
56
|
+
val = '(\\S*)' if p.include?(':')
|
57
|
+
out + '[/]' + val
|
58
|
+
end
|
59
|
+
reg_ex_path.push(Regexp.new(regex_paths))
|
60
|
+
end
|
61
|
+
reg_ex_path
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
require_relative 'railtie' if defined?(Rails)
|
5
|
+
|
6
|
+
module Spokes
|
7
|
+
module Versioning
|
8
|
+
# Minor versioning mix-in for controllers.
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
#
|
12
|
+
# # app/controllers/my_controller.rb
|
13
|
+
# class MyController
|
14
|
+
# include MinorVersioning
|
15
|
+
#
|
16
|
+
# def index
|
17
|
+
# logger.info(minor_version)
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
module MinorVersioning
|
22
|
+
extend ActiveSupport::Concern
|
23
|
+
|
24
|
+
API_VERSION = 'API-Version'.freeze
|
25
|
+
|
26
|
+
included do
|
27
|
+
include MinorVersioning
|
28
|
+
after_filter :set_minor_version_response_header
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_minor_version_response_header
|
32
|
+
response.headers[API_VERSION] = minor_version
|
33
|
+
end
|
34
|
+
|
35
|
+
def minor_version
|
36
|
+
@minor_version ||= begin
|
37
|
+
chosen_version = request.headers[API_VERSION]
|
38
|
+
return chosen_version if valid_minor_version?(chosen_version)
|
39
|
+
default_minor_version
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def default_minor_version
|
46
|
+
@default_minor_version ||= begin
|
47
|
+
default = find_default_version
|
48
|
+
raise('No version marked as default in the configuration.') if default.nil?
|
49
|
+
default
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_default_version
|
54
|
+
all_minor_versions.each do |version, info|
|
55
|
+
return version if info[:default]
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def valid_minor_version?(version)
|
61
|
+
version.present? && all_minor_versions.keys.include?(version)
|
62
|
+
end
|
63
|
+
|
64
|
+
def all_minor_versions
|
65
|
+
has_versions = Rails.application.config.respond_to?(:minor_versions)
|
66
|
+
raise('config/minor_versions.yml doesn\'t exist.') unless has_versions
|
67
|
+
Rails.application.config.minor_versions
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rails'
|
4
|
+
|
5
|
+
module Spokes
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer('spokes_versioning.load_minor_versions_file') do |app|
|
8
|
+
config_file = Rails.root.join('config', 'minor_versions.yml')
|
9
|
+
if File.exist?(config_file)
|
10
|
+
minor_versions = YAML.load_file(config_file)
|
11
|
+
app.config.minor_versions = {}
|
12
|
+
minor_versions.map do |key, val|
|
13
|
+
app.config.minor_versions[key] = val.symbolize_keys
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
rake_tasks do
|
19
|
+
load 'spokes/versioning/tasks/minor_versioning.rake'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
namespace :spokes do
|
4
|
+
namespace :versioning do
|
5
|
+
MINOR_VERSION_YML = File.join(File.dirname(__FILE__), '../config/minor_versions.yml').to_s.freeze
|
6
|
+
|
7
|
+
desc 'Sets up minor versioning yml'
|
8
|
+
task setup: :environment do
|
9
|
+
raise 'Minor version currently only supports Rails Applications' unless defined?(Rails)
|
10
|
+
next if File.exist?("#{Rails.root}/config/minor_versions.yml")
|
11
|
+
FileUtils.cp(MINOR_VERSION_YML, "#{Rails.root}/config", verbose: true)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
Binary file
|
data/script/cibuild
ADDED
data/script/postbuild
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# script/postbuild: Cleanup environment after CI. This is primarily
|
4
|
+
# designed to run on the continuous integration server.
|
5
|
+
|
6
|
+
set -e
|
7
|
+
|
8
|
+
PROJECT_NAME='spokes'
|
9
|
+
|
10
|
+
[[ "${PROJECT_NAME:-}" ]] || (echo "PROJECT_NAME is required." && exit 1)
|
11
|
+
|
12
|
+
# cd to project root
|
13
|
+
cd "$(dirname "$0")/.."
|
14
|
+
|
15
|
+
docker stop `docker ps -a -q -f status=exited` &> /dev/null || true &> /dev/null
|
16
|
+
docker rm -v `docker ps -a -q -f status=exited` &> /dev/null || true &> /dev/null
|
17
|
+
docker rmi `docker images --filter 'dangling=true' -q --no-trunc` &> /dev/null || true &> /dev/null
|
data/script/test
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# script/test: Run test suite for application. Optionally pass in a path to an
|
4
|
+
# individual test file to run a single test.
|
5
|
+
|
6
|
+
set -e
|
7
|
+
set -u
|
8
|
+
|
9
|
+
export RAILS_ENV="test" RACK_ENV="test"
|
10
|
+
PROJECT_NAME='spokes'
|
11
|
+
|
12
|
+
[[ "${PROJECT_NAME:-}" ]] || (echo "PROJECT_NAME is required." && exit 1)
|
13
|
+
|
14
|
+
# cd to project root
|
15
|
+
cd "$(dirname "$0")/.."
|
16
|
+
|
17
|
+
# Build deploy Docker image
|
18
|
+
docker build --tag=$PROJECT_NAME .
|
19
|
+
|
20
|
+
printf "\n===> Running tests ...\n"
|
21
|
+
date "+%H:%M:%S"
|
22
|
+
|
23
|
+
docker run --rm $PROJECT_NAME bundle exec rspec
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spokes::Config::Env do
|
4
|
+
after do
|
5
|
+
%w[HOMER_SIMPSON TEST_STRING_VAR TEST_INT_VAR TEST_FLOAT_VAR TEST_ARRAY_VAR TEST_SYMBOL_VAR TEST_BOOL_VAR].each do |w|
|
6
|
+
ENV.delete(w)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#manditory' do
|
11
|
+
let(:env_loader) do
|
12
|
+
lambda do
|
13
|
+
Spokes::Config::Env.load do
|
14
|
+
mandatory :homer_simpson, string
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'raises error if variable is not set' do
|
20
|
+
expect(env_loader).to raise_error(KeyError)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'exposes variable once loaded' do
|
24
|
+
ENV['HOMER_SIMPSON'] = 'doh'
|
25
|
+
expect(env_loader.call.homer_simpson).to eq('doh')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#optional' do
|
30
|
+
let(:env_loader) do
|
31
|
+
lambda do
|
32
|
+
Spokes::Config::Env.load do
|
33
|
+
optional :homer_simpson, string
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'exposes variable once loaded' do
|
39
|
+
ENV['HOMER_SIMPSON'] = 'doh'
|
40
|
+
expect(env_loader.call.homer_simpson).to eq('doh')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'value is nil when variable is not set' do
|
44
|
+
expect(env_loader.call.homer_simpson).to eq(nil)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#default' do
|
49
|
+
let(:env_loader) do
|
50
|
+
lambda do
|
51
|
+
Spokes::Config::Env.load do
|
52
|
+
default :homer_simpson, 'derp', string
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'exposes variable once loaded' do
|
58
|
+
ENV['HOMER_SIMPSON'] = 'doh'
|
59
|
+
expect(env_loader.call.homer_simpson).to eq('doh')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'uses default value when varible is not set' do
|
63
|
+
expect(env_loader.call.homer_simpson).to eq('derp')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'type casting' do
|
68
|
+
let(:env_loader) do
|
69
|
+
lambda do
|
70
|
+
Spokes::Config::Env.load do
|
71
|
+
optional :test_bool_var, bool
|
72
|
+
optional :test_float_var, float
|
73
|
+
optional :test_string_var, string
|
74
|
+
optional :test_symbol_var, symbol
|
75
|
+
optional :test_array_var, array
|
76
|
+
optional :test_int_var, int
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'does nothing to strings' do
|
82
|
+
ENV['TEST_STRING_VAR'] = 'stringy'
|
83
|
+
expect(env_loader.call.test_string_var).to eq('stringy')
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'casts to integer' do
|
87
|
+
ENV['TEST_INT_VAR'] = '123'
|
88
|
+
expect(env_loader.call.test_int_var).to eq(123)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'casts to float' do
|
92
|
+
ENV['TEST_FLOAT_VAR'] = '1.23'
|
93
|
+
expect(env_loader.call.test_float_var).to eq(1.23)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'casts to array' do
|
97
|
+
ENV['TEST_ARRAY_VAR'] = 'hello,world'
|
98
|
+
expect(env_loader.call.test_array_var).to eq(%w[hello world])
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'casts to symbol' do
|
102
|
+
ENV['TEST_SYMBOL_VAR'] = 'howdy'
|
103
|
+
expect(env_loader.call.test_symbol_var).to eq(:howdy)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'casts to boolean' do
|
107
|
+
ENV['TEST_BOOL_VAR'] = 'true'
|
108
|
+
expect(env_loader.call.test_bool_var).to eq(true)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spokes::Middleware::CORS, modules: true, middleware: true do
|
4
|
+
let(:app) { proc { [200, {}, ['hi']] } }
|
5
|
+
let(:stack) { Spokes::Middleware::CORS.new(app) }
|
6
|
+
let(:request) { Rack::MockRequest.new(stack) }
|
7
|
+
|
8
|
+
it 'does not do anything when the Origin header is not present' do
|
9
|
+
response = request.get('/')
|
10
|
+
expect(response.status).to eq(200)
|
11
|
+
expect(response.body).to eq('hi')
|
12
|
+
expect(response.headers['Access-Control-Allow-Origin']).to eq(nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'intercepts OPTION requests to render a stub (preflight request)' do
|
16
|
+
response = request.options('/', 'Origin' => 'http://localhost', 'HTTP_ORIGIN' => 'http://localhost')
|
17
|
+
expect(response.status).to eq(200)
|
18
|
+
expect(response.body).to eq('')
|
19
|
+
expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, POST, PUT, PATCH, DELETE, OPTIONS')
|
20
|
+
expect(response.headers['Access-Control-Allow-Origin']).to eq('http://localhost')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'delegates other calls, adding the CORS headers to the response' do
|
24
|
+
response = request.get('/', 'Origin' => 'http://localhost', 'HTTP_ORIGIN' => 'http://localhost')
|
25
|
+
expect(response.status).to eq(200)
|
26
|
+
expect(response.body).to eq('hi')
|
27
|
+
expect(response.headers['Access-Control-Allow-Origin']).to eq('http://localhost')
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spokes::Middleware::Health, modules: true, middleware: true do
|
4
|
+
def env(url = '/', *args)
|
5
|
+
Rack::MockRequest.env_for(url, *args)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:base_app) do
|
9
|
+
lambda do |_env|
|
10
|
+
[200, { 'Content-Type' => 'text/plain' }, ['Oi!']]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:app) { Rack::Lint.new Spokes::Middleware::Health.new(base_app, health_options) }
|
15
|
+
let(:health_options) { {} }
|
16
|
+
let(:status) { subject[0] }
|
17
|
+
let(:body) do
|
18
|
+
str = ''
|
19
|
+
subject[2].each do |s|
|
20
|
+
str += s
|
21
|
+
end
|
22
|
+
str
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'with default options' do
|
26
|
+
let(:health_options) { {} }
|
27
|
+
|
28
|
+
describe '/' do
|
29
|
+
subject { app.call env('/') }
|
30
|
+
it { expect(status).to eq(200) }
|
31
|
+
it { expect(body).to eq('Oi!') }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '/status' do
|
35
|
+
subject { app.call env('/status') }
|
36
|
+
it { expect(status).to eq(200) }
|
37
|
+
it { expect(body).to eq('OK') }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'as json' do
|
42
|
+
let(:health_options) { {} }
|
43
|
+
|
44
|
+
describe '/' do
|
45
|
+
subject { app.call env('/', 'Content-Type' => 'application/json') }
|
46
|
+
it { expect(status).to eq(200) }
|
47
|
+
it { expect(body).to eq('Oi!') }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '/status' do
|
51
|
+
subject { app.call env('/status', 'CONTENT_TYPE' => 'application/json') }
|
52
|
+
it { expect(status).to eq(200) }
|
53
|
+
it { expect(JSON.parse(body)['status']).to eq('OK') }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'with :fail_if' do
|
58
|
+
subject { app.call env('/status') }
|
59
|
+
|
60
|
+
describe '== lambda { true }' do
|
61
|
+
let(:health_options) { { fail_if: -> { true } } }
|
62
|
+
|
63
|
+
it { expect(status).to eq(503) }
|
64
|
+
it { expect(body).to eq('FAIL') }
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '== lambda { false }' do
|
68
|
+
let(:health_options) { { fail_if: -> { false } } }
|
69
|
+
it { expect(status).to eq(200) }
|
70
|
+
it { expect(body).to eq('OK') }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'with :status_code' do
|
75
|
+
let(:status_proc) { ->(healthy) { healthy ? 202 : 404 } }
|
76
|
+
subject { app.call env('/status') }
|
77
|
+
|
78
|
+
context 'healthy' do
|
79
|
+
let(:health_options) { { fail_if: -> { false }, status_code: status_proc } }
|
80
|
+
it { expect(status).to eq(202) }
|
81
|
+
it { expect(body).to eq('OK') }
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'fail' do
|
85
|
+
let(:health_options) { { fail_if: -> { true }, status_code: status_proc } }
|
86
|
+
it { expect(status).to eq(404) }
|
87
|
+
it { expect(body).to eq('FAIL') }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'with :simple' do
|
92
|
+
let(:body_proc) { ->(healthy) { healthy ? 'GOOD' : 'BAD' } }
|
93
|
+
subject { app.call env('/status') }
|
94
|
+
|
95
|
+
context 'healthy' do
|
96
|
+
let(:health_options) { { fail_if: -> { false }, simple: body_proc } }
|
97
|
+
it { expect(status).to eq(200) }
|
98
|
+
it { expect(body).to eq('GOOD') }
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'fail' do
|
102
|
+
let(:health_options) { { fail_if: -> { true }, simple: body_proc } }
|
103
|
+
it { expect(status).to eq(503) }
|
104
|
+
it { expect(body).to eq('BAD') }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|