bandiera-client 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 748f5858d09ce6aec023da7987c3f4ab0fc0fa75
4
+ data.tar.gz: e9cb4b7fa6ba2e12f04b24cb7bc8ca7505b5313a
5
+ SHA512:
6
+ metadata.gz: 75bbcece7e445f4420c8d4e8bb8091372705e435d4e5d81625ce8cc6d1c02ac71fee56846ded50c5b59360c5c41f5a55aa5f62adc41510914b847e62751f1d60
7
+ data.tar.gz: e42c294b272343d9a495aa091c90dfcb72bfa7c7d1880f4a6645b8c08404c4befe0be8aeb2e3b4f3c5e26564ab01bdb6a6baddce4f1da8bdd74abdace2b4a6a6
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ artifacts*
24
+ RBENV_VERSION*
data/.hound.yml ADDED
@@ -0,0 +1,36 @@
1
+ Metrics/LineLength:
2
+ Description: 'Limit lines to 120 characters.'
3
+ Max: 120
4
+
5
+ Style/Documentation:
6
+ Enabled: false
7
+
8
+ Style/SingleSpaceBeforeFirstArg:
9
+ Enabled: false
10
+
11
+ Style/BracesAroundHashParameters:
12
+ Enabled: false
13
+
14
+ Style/IndentHash:
15
+ EnforcedStyle: consistent
16
+
17
+ Style/AlignHash:
18
+ EnforcedHashRocketStyle: table
19
+ EnforcedColonStyle: table
20
+
21
+ Style/AlignParameters:
22
+ EnforcedStyle: with_fixed_indentation
23
+
24
+ Style/StringLiterals:
25
+ EnforcedStyle: single_quotes
26
+
27
+ Style/CollectionMethods:
28
+ PreferredMethods:
29
+ collect: 'map'
30
+ collect!: 'map!'
31
+ inject: 'reduce'
32
+ detect: 'find'
33
+ find_all: 'select'
34
+
35
+ Style/DotPosition:
36
+ EnforcedStyle: leading
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --profile
3
+
data/.rubocop.yml ADDED
@@ -0,0 +1,36 @@
1
+ Metrics/LineLength:
2
+ Description: 'Limit lines to 120 characters.'
3
+ Max: 120
4
+
5
+ Style/Documentation:
6
+ Enabled: false
7
+
8
+ Style/SingleSpaceBeforeFirstArg:
9
+ Enabled: false
10
+
11
+ Style/BracesAroundHashParameters:
12
+ Enabled: false
13
+
14
+ Style/IndentHash:
15
+ EnforcedStyle: consistent
16
+
17
+ Style/AlignHash:
18
+ EnforcedHashRocketStyle: table
19
+ EnforcedColonStyle: table
20
+
21
+ Style/AlignParameters:
22
+ EnforcedStyle: with_fixed_indentation
23
+
24
+ Style/StringLiterals:
25
+ EnforcedStyle: single_quotes
26
+
27
+ Style/CollectionMethods:
28
+ PreferredMethods:
29
+ collect: 'map'
30
+ collect!: 'map!'
31
+ inject: 'reduce'
32
+ detect: 'find'
33
+ find_all: 'select'
34
+
35
+ Style/DotPosition:
36
+ EnforcedStyle: leading
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/HISTORY ADDED
@@ -0,0 +1,39 @@
1
+ = 2.2.2 / 02-March-2015
2
+
3
+ * Handle non-JSON responses correctly (these can happen due to proxy issues etc).
4
+
5
+ = 2.2.1 / 19-January-2015
6
+
7
+ * Small updates for Ruby 2.2.0 compatability.
8
+ * Documentation updates.
9
+
10
+ = 2.2.0 / 14-August-2014
11
+
12
+ * Add a configurable caching layer into the client.
13
+ * Expose methods to get raw responses from the Bandiera API
14
+
15
+ = 2.1.0 / 02-July-2014
16
+
17
+ * Switch license to MIT.
18
+ * Handle connection refused errors.
19
+
20
+ = 2.0.2 / 15-May-2014
21
+
22
+ * Switch the HTTP library used from Typhoeus to RestClient.
23
+ * Set the default HTTP timeout to 0.2 seconds.
24
+ * Allow per-request timeout (and other RestClient::Resource options) to be passed through.
25
+
26
+ = 2.0.1 / 07-May-2014
27
+
28
+ * Enable test suite to be run via Rake.
29
+ * Allow a client name to be passed through in the HTTP headers as 'Bandiera-Client'.
30
+ * Set the 'User-Agent' HTTP header to identify the requests as coming from this client.
31
+
32
+ = 2.0.0 / 01-May-2014
33
+
34
+ * Update to support Bandiera API v2 and user_groups feature.
35
+
36
+ = 1.0.0 / 24-Jan-2014
37
+
38
+ * Initial tagging of 1.0.0 codebase - support for Bandiera API v1.
39
+
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+
2
+ Copyright (c) 2014, Nature Publishing Group
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # Bandiera::Client (Ruby)
2
+
3
+ This is a client for talking to the [Bandiera][bandiera] feature flagging
4
+ service from a Ruby application.
5
+
6
+ This client is compatible with the [v2 Bandiera API][bandiera-api].
7
+
8
+ **Current Version:** 2.2.1
9
+ **License:** [MIT][mit]
10
+ **Build Status:** [![Build Status][travis-img]][travis]
11
+
12
+ ## Ruby Support:
13
+
14
+ This client has been tested against the latest MRI and JRuby builds.
15
+
16
+ # Usage
17
+
18
+ Add the following to your `Gemfile`:
19
+
20
+ ```ruby
21
+ gem 'bandiera-client'
22
+ ```
23
+
24
+ Then interact with a Bandiera server like so:
25
+
26
+ ```ruby
27
+ require 'bandiera/client'
28
+
29
+ $bandiera = Bandiera::Client.new('http://bandiera.example.com')
30
+
31
+ if $bandiera.enabled?('pubserv', 'show-new-search')
32
+ # show the new experimental search function
33
+ end
34
+ ```
35
+
36
+ The `$bandiera.enabled?` command takes two arguments - the 'feature group',
37
+ and the 'feature name'. This is because in Bandiera, features are organised
38
+ in groups as it is intented as a service for multiple applications to use at
39
+ the same time - this organisation allows separation of feature flags that are
40
+ intended for different audiences.
41
+
42
+ ## Caching
43
+
44
+ Bandiera::Client has a small layer of caching built into it in order to:
45
+
46
+ 1. Reduce the amount of HTTP requests made to the Bandiera server
47
+ 2. Make things faster
48
+
49
+ ### Strategies
50
+
51
+ There are three request/caching strategies you can use with Bandiera::Client.
52
+
53
+ **:single_feature**
54
+
55
+ This strategy calls the Bandiera API for each new .enabled? request, and stores
56
+ the response in it's local cache. Subsequent `.enabled?` requests (using the
57
+ same arguments) will read from the cache until it has expired. This is the
58
+ **least** efficient strategy in terms of reducing HTTP requests and speed.
59
+
60
+ **:group**
61
+
62
+ This strategy calls the Bandiera API **once** for each feature flag group
63
+ requested, and then stores the resulting feature flag values in the cache.
64
+ This means that all subsequent calls to `.enabled?` for the same group will not
65
+ perform a HTTP request and instead read from the cache until it has expired.
66
+ This is a good compromise in terms of speed and number of HTTP requests, **and
67
+ is the default caching strategy**.
68
+
69
+ **:all**
70
+
71
+ This strategy calls the Bandiera API **once** and fetches/caches all feature
72
+ flag values locally. All subsequent calls to `.enabled?` read from the cache
73
+ until it has expired. This strategy is obviously the most efficient in terms of
74
+ the number of HTTP requests if you are requesting flags from across multiple
75
+ groups, but might not be the fastest if there are **lots** of flags in your
76
+ Bandiera instance.
77
+
78
+ #### Changing the Cache Strategy
79
+
80
+ ```ruby
81
+ $bandiera = Bandiera::Client.new('http://bandiera.example.com')
82
+ $bandiera.cache_strategy = :all
83
+ ```
84
+
85
+ ### Cache Expiration
86
+
87
+ The default cache lifetime is 5 seconds. If you would like to alter this you
88
+ can do so as follows:
89
+
90
+ ```ruby
91
+ $bandiera = Bandiera::Client.new('http://bandiera.example.com')
92
+ $bandiera.cache_ttl = 10 # 10 seconds
93
+ ```
94
+
95
+
96
+ # Development
97
+
98
+ 1. Fork this repo.
99
+ 2. Run `bundle install`
100
+
101
+ # License
102
+
103
+ [© 2014 Nature Publishing Group](LICENSE.txt).
104
+ Bandiera::Client (Ruby) is licensed under the [MIT License][mit].
105
+
106
+
107
+ [mit]: http://opensource.org/licenses/mit-license.php
108
+ [bandiera]: https://github.com/nature/bandiera
109
+ [bandiera-api]: https://github.com/nature/bandiera/wiki/API-Documentation
110
+ [travis]: https://travis-ci.org/nature/bandiera-client-ruby
111
+ [travis-img]: https://travis-ci.org/nature/bandiera-client-ruby.svg?branch=master
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'bandiera/client/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'bandiera-client'
10
+ spec.version = Bandiera::Client::VERSION
11
+ spec.authors = ['Macmillan Science and Education (New Publsihing Platforms)']
12
+ spec.email = ['npp-developers@macmillan.com']
13
+ spec.description = 'Bandiera is a simple, stand-alone feature flagging service that is not tied to any existing web framework or language. This is a client for talking to the web service.'
14
+ spec.summary = 'Simple feature flagging API client.'
15
+ spec.homepage = 'https://github.com/nature/bandiera-client-ruby'
16
+ spec.license = 'GPL-3'
17
+
18
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
19
+ spec.test_files = Dir.glob('spec/*_spec.rb')
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'webmock'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'pry'
27
+
28
+ spec.add_dependency 'rest-client'
29
+ spec.add_dependency 'moneta'
30
+ spec.add_dependency 'macmillan-utils'
31
+ end
@@ -0,0 +1,5 @@
1
+ module Bandiera
2
+ class Client
3
+ VERSION = '2.2.2'
4
+ end
5
+ end
@@ -0,0 +1,140 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+ require 'logger'
4
+ require 'moneta'
5
+
6
+ module Bandiera
7
+ class Client
8
+ autoload :VERSION, 'bandiera/client/version'
9
+
10
+ CACHE_STRATEGIES = [:single_feature, :group, :all]
11
+
12
+ attr_accessor :timeout, :client_name, :cache_ttl
13
+ attr_reader :logger, :cache, :cache_strategy
14
+
15
+ def initialize(base_uri = 'http://localhost', logger = Logger.new($stdout), client_name = nil)
16
+ @base_uri = base_uri
17
+ @base_uri << '/api' unless @base_uri.match(/\/api$/)
18
+ @logger = logger
19
+ @timeout = 0.2 # 0.4s (0.2 + 0.2) default timeout
20
+ @client_name = client_name
21
+ @cache = Moneta.new(:LRUHash, expires: true)
22
+ @cache_ttl = 5 # 5 seconds
23
+ @cache_strategy = :group
24
+ end
25
+
26
+ def cache_strategy=(strategy)
27
+ unless CACHE_STRATEGIES.include?(strategy)
28
+ raise ArgumentError, "cache_strategy can only be #{CACHE_STRATEGIES}"
29
+ end
30
+ @cache_strategy = strategy
31
+ end
32
+
33
+ def enabled?(group, feature, params = {}, http_opts = {})
34
+ cache_key = build_cache_key(group, feature, params)
35
+
36
+ unless cache.key?(cache_key)
37
+ case cache_strategy
38
+ when :single_feature then get_feature(group, feature, params, http_opts)
39
+ when :group then get_features_for_group(group, params, http_opts)
40
+ when :all then get_all
41
+ end
42
+ end
43
+
44
+ cache.fetch(cache_key)
45
+ end
46
+
47
+ def get_feature(group, feature, params = {}, http_opts = {})
48
+ path = "/v2/groups/#{group}/features/#{feature}"
49
+ default_response = false
50
+ error_msg_prefix = "[Bandiera::Client#get_feature] '#{group} / #{feature} / #{params}'"
51
+
52
+ logger.debug "[Bandiera::Client#get_feature] calling #{path} with params: #{params}"
53
+
54
+ get_and_handle_exceptions(path, params, http_opts, default_response, error_msg_prefix) do |value|
55
+ store_value_in_cache(group, feature, params, value)
56
+ end
57
+ end
58
+
59
+ def get_features_for_group(group, params = {}, http_opts = {})
60
+ path = "/v2/groups/#{group}/features"
61
+ default_response = {}
62
+ error_msg_prefix = "[Bandiera::Client#get_features_for_group] '#{group} / #{params}'"
63
+
64
+ logger.debug "[Bandiera::Client#get_features_for_group] calling #{path} with params: #{params}"
65
+
66
+ get_and_handle_exceptions(path, params, http_opts, default_response, error_msg_prefix) do |feature_hash|
67
+ store_feature_hash_in_cache(group, params, feature_hash)
68
+ end
69
+ end
70
+
71
+ def get_all(params = {}, http_opts = {})
72
+ path = '/v2/all'
73
+ default_response = {}
74
+ error_msg_prefix = "[Bandiera::Client#get_all] '#{params}'"
75
+
76
+ logger.debug "[Bandiera::Client#get_all] calling #{path} with params: #{params}"
77
+
78
+ get_and_handle_exceptions(path, params, http_opts, default_response, error_msg_prefix) do |group_hash|
79
+ group_hash.each do |group, feature_hash|
80
+ store_feature_hash_in_cache(group, params, feature_hash)
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def store_feature_hash_in_cache(group, params, feature_hash)
88
+ feature_hash.each do |feature, value|
89
+ store_value_in_cache(group, feature, params, value)
90
+ end
91
+ end
92
+
93
+ def store_value_in_cache(group, feature, params, value)
94
+ cache_key = build_cache_key(group, feature, params)
95
+ cache.store(cache_key, value, expires: cache_ttl)
96
+ end
97
+
98
+ def build_cache_key(group, feature, params)
99
+ "#{group} / #{feature} / #{params}"
100
+ end
101
+
102
+ def headers
103
+ headers = { 'User-Agent' => "Bandiera Ruby Client / #{Bandiera::Client::VERSION}" }
104
+ headers.merge!('Bandiera-Client' => client_name) unless client_name.nil?
105
+ headers
106
+ end
107
+
108
+ EXCEPTIONS_TO_HANDLE = (
109
+ Errno.constants.map { |cla| Errno.const_get(cla) } + [RestClient::Exception, JSON::ParserError]
110
+ ).flatten
111
+
112
+ def get_and_handle_exceptions(path, params, http_opts, return_upon_error, error_msg_prefix, &block)
113
+ res = get(path, params, http_opts)
114
+ logger.warn "#{error_msg_prefix} - #{res['warning']}" if res['warning']
115
+ block.call(res['response']) if block
116
+ res['response']
117
+ rescue *EXCEPTIONS_TO_HANDLE => error
118
+ logger.warn("#{error_msg_prefix} - #{error.class} - #{error.message}")
119
+ return_upon_error
120
+ end
121
+
122
+ def get(path, params, passed_http_opts)
123
+ default_http_opts = { method: :get, timeout: timeout, open_timeout: timeout, headers: headers }
124
+ resource = RestClient::Resource.new(@base_uri, default_http_opts.merge(passed_http_opts))
125
+ response = resource[path].get(params: clean_params(params))
126
+
127
+ JSON.parse(response.body)
128
+ end
129
+
130
+ def clean_params(passed_params)
131
+ params = {}
132
+
133
+ passed_params.each do |key, val|
134
+ params[key] = val unless val.nil? || (val.respond_to?(:empty) && val.empty?)
135
+ end
136
+
137
+ params
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,472 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bandiera::Client do
4
+ let(:base_uri) { 'http://bandiera.com' }
5
+ let(:api_uri) { "#{base_uri}/api" }
6
+ let(:logger) { double.as_null_object }
7
+ subject { Bandiera::Client.new(api_uri, logger) }
8
+
9
+ context 'when a client name is provided' do
10
+ let(:group) { 'pubserv' }
11
+ let(:feature) { 'log-stats' }
12
+ let(:url) { "#{api_uri}/v2/groups/#{group}/features" }
13
+
14
+ it 'sends it as part of the headers' do
15
+ stub = stub_api_request(url, { 'response' => {} }, { 'Bandiera-Client' => 'asdf' })
16
+ client = Bandiera::Client.new(api_uri, logger, 'asdf')
17
+ client.enabled?(group, feature)
18
+ expect(stub).to have_been_requested
19
+ end
20
+ end
21
+
22
+ context 'when some RestClient::Resource options are passed' do
23
+ it 'passes them onto the RestClient::Resource' do
24
+ params = {}
25
+ options = { timeout: 24, open_timeout: 24 }
26
+ response = double(:response, body: JSON.generate({ response: {} }))
27
+ resource = double(:resource, '[]' => double(get: response))
28
+
29
+ expect(RestClient::Resource)
30
+ .to receive(:new)
31
+ .with(api_uri, method: :get, timeout: 24, open_timeout: 24, headers: anything)
32
+ .once
33
+ .and_return(resource)
34
+
35
+ subject.enabled?('foo', 'bar', params, options)
36
+ end
37
+ end
38
+
39
+ describe '#enabled?' do
40
+ context 'with cache strategy :single_feature' do
41
+ before do
42
+ subject.cache_strategy = :single_feature
43
+ end
44
+
45
+ context 'and an empty cache' do
46
+ it 'calls #get_feature' do
47
+ expect(subject).to receive(:get_feature).once
48
+ subject.enabled?('foo', 'bar')
49
+ end
50
+ end
51
+
52
+ context 'and a primed cache' do
53
+ before do
54
+ cache_key = subject.send(:build_cache_key, 'foo', 'bar', {})
55
+ subject.cache.store(cache_key, false)
56
+ end
57
+
58
+ it 'does not call #get_feature' do
59
+ expect(subject).to_not receive(:get_feature)
60
+ subject.enabled?('foo', 'bar')
61
+ end
62
+ end
63
+ end
64
+
65
+ context 'with cache strategy :group' do
66
+ before do
67
+ subject.cache_strategy = :group
68
+ end
69
+
70
+ context 'and an empty cache' do
71
+ it 'calls #get_features_for_group' do
72
+ expect(subject).to receive(:get_features_for_group).once
73
+ subject.enabled?('foo', 'bar')
74
+ end
75
+ end
76
+
77
+ context 'and a primed cache' do
78
+ before do
79
+ cache_key = subject.send(:build_cache_key, 'foo', 'bar', {})
80
+ subject.cache.store(cache_key, false)
81
+ end
82
+
83
+ it 'does not call #get_features_for_group' do
84
+ expect(subject).to_not receive(:get_features_for_group)
85
+ subject.enabled?('foo', 'bar')
86
+ end
87
+ end
88
+ end
89
+
90
+ context 'with cache strategy :all' do
91
+ before do
92
+ subject.cache_strategy = :all
93
+ end
94
+
95
+ context 'and an empty cache' do
96
+ it 'calls #get_all' do
97
+ expect(subject).to receive(:get_all).once
98
+ subject.enabled?('foo', 'bar')
99
+ end
100
+ end
101
+
102
+ context 'and a primed cache' do
103
+ before do
104
+ cache_key = subject.send(:build_cache_key, 'foo', 'bar', {})
105
+ subject.cache.store(cache_key, false)
106
+ end
107
+
108
+ it 'does not call #get_all' do
109
+ expect(subject).to_not receive(:get_all)
110
+ subject.enabled?('foo', 'bar')
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ describe '#get_feature' do
117
+ let(:group) { 'pubserv' }
118
+ let(:feature) { 'log-stats' }
119
+ let(:url) { "#{api_uri}/v2/groups/#{group}/features/#{feature}" }
120
+
121
+ context 'all is ok' do
122
+ it 'returns the bandiera response' do
123
+ stub = stub_api_request(url, 'response' => true)
124
+ response = subject.get_feature(group, feature)
125
+
126
+ expect(response).to be true
127
+ expect(stub).to have_been_requested
128
+ end
129
+
130
+ it 'stores the feature value in the cache' do
131
+ stub_api_request(url, 'response' => true)
132
+ cache_key = subject.send(:build_cache_key, group, feature, {})
133
+
134
+ subject.get_feature(group, feature)
135
+ expect(subject.cache.key?(cache_key)).to be true
136
+ end
137
+
138
+ context 'and the user has passed through some extra params' do
139
+ it 'passes them through to the API' do
140
+ stub = stub_request(:get, url)
141
+ .with(query: { user_group: 'admin', user_id: '12345' })
142
+ .to_return(
143
+ body: JSON.generate('response' => true),
144
+ headers: { 'Content-Type' => 'application/json' }
145
+ )
146
+
147
+ subject.get_feature(group, feature, { user_group: 'admin', user_id: 12345 })
148
+
149
+ expect(stub).to have_been_requested
150
+ end
151
+ end
152
+
153
+ context 'but bandiera returns a warning along with the response' do
154
+ it 'logs the warning' do
155
+ stub = stub_api_request(url, 'response' => false, 'warning' => 'The group does not exist')
156
+
157
+ expect(logger).to receive(:warn).once
158
+
159
+ response = subject.get_feature(group, feature)
160
+
161
+ expect(response).to be false
162
+ expect(stub).to have_been_requested
163
+ end
164
+ end
165
+ end
166
+
167
+ context 'bandiera is down' do
168
+ it 'returns a default response and logs a warning' do
169
+ stub_request(:get, url).to_return(status: [0, ''])
170
+
171
+ expect(logger).to receive(:warn).once
172
+
173
+ response = subject.get_feature(group, feature)
174
+
175
+ expect(response).to be false
176
+ end
177
+
178
+ it 'does not store anything in the cache' do
179
+ stub_request(:get, url).to_return(status: [0, ''])
180
+
181
+ expect(subject.cache).to_not receive(:store)
182
+
183
+ subject.get_feature(group, feature)
184
+ end
185
+ end
186
+
187
+ context 'bandiera is having some problems' do
188
+ it 'returns a default response and logs a warning' do
189
+ stub_request(:get, url).to_return(status: 500, body: '')
190
+
191
+ expect(logger).to receive(:warn).once
192
+
193
+ response = subject.get_feature(group, feature)
194
+
195
+ expect(response).to be false
196
+ end
197
+
198
+ it 'does not store anything in the cache' do
199
+ stub_request(:get, url).to_return(status: 500, body: '')
200
+
201
+ expect(subject.cache).to_not receive(:store)
202
+
203
+ subject.get_feature(group, feature)
204
+ end
205
+ end
206
+
207
+ context 'bandiera times out' do
208
+ it 'returns a default response and logs a warning' do
209
+ stub_request(:get, url).to_timeout
210
+
211
+ expect(logger).to receive(:warn).once
212
+
213
+ response = subject.get_feature(group, feature)
214
+
215
+ expect(response).to be false
216
+ end
217
+
218
+ it 'does not store anything in the cache' do
219
+ stub_request(:get, url).to_timeout
220
+
221
+ expect(subject.cache).to_not receive(:store)
222
+
223
+ subject.get_feature(group, feature)
224
+ end
225
+ end
226
+ end
227
+
228
+ describe '#get_features_for_group' do
229
+ let(:group) { 'pubserv' }
230
+ let(:url) { "#{api_uri}/v2/groups/#{group}/features" }
231
+
232
+ context 'all is ok' do
233
+ it 'returns the bandiera response' do
234
+ feature_hash = { 'show-stuff' => true, 'show-other-stuff' => false }
235
+ stub = stub_api_request(url, 'response' => feature_hash)
236
+ response = subject.get_features_for_group(group)
237
+
238
+ expect(response).to eq(feature_hash)
239
+ expect(stub).to have_been_requested
240
+ end
241
+
242
+ it 'stores the feature values in the cache' do
243
+ feature_hash = { 'show-stuff' => true, 'show-other-stuff' => false }
244
+ stub_api_request(url, 'response' => feature_hash)
245
+
246
+ subject.get_features_for_group(group)
247
+
248
+ cache_key = subject.send(:build_cache_key, group, 'show-stuff', {})
249
+ expect(subject.cache.key?(cache_key)).to be true
250
+
251
+ cache_key = subject.send(:build_cache_key, group, 'show-other-stuff', {})
252
+ expect(subject.cache.key?(cache_key)).to be true
253
+ end
254
+
255
+ context 'and the user has passed through some extra params' do
256
+ it 'passes them through to the API' do
257
+ stub = stub_request(:get, url)
258
+ .with(query: { user_group: 'admin', user_id: '12345' })
259
+ .to_return(
260
+ body: JSON.generate('response' => {}),
261
+ headers: { 'Content-Type' => 'application/json' }
262
+ )
263
+
264
+ subject.get_features_for_group(group, { user_group: 'admin', user_id: 12345 })
265
+
266
+ expect(stub).to have_been_requested
267
+ end
268
+ end
269
+
270
+ context 'but bandiera returns a warning along with the response' do
271
+ it 'logs the warning' do
272
+ stub = stub_api_request(url, 'response' => {}, 'warning' => 'The group does not exist')
273
+
274
+ expect(logger).to receive(:warn).once
275
+
276
+ response = subject.get_features_for_group(group)
277
+
278
+ expect(response).to be {}
279
+ expect(stub).to have_been_requested
280
+ end
281
+ end
282
+ end
283
+
284
+ context 'bandiera is down' do
285
+ it 'returns a default response and logs a warning' do
286
+ stub_request(:get, url).to_return(status: [0, ''])
287
+
288
+ expect(logger).to receive(:warn).once
289
+
290
+ response = subject.get_features_for_group(group)
291
+
292
+ expect(response).to be {}
293
+ end
294
+
295
+ it 'does not store anything in the cache' do
296
+ stub_request(:get, url).to_return(status: [0, ''])
297
+
298
+ expect(subject.cache).to_not receive(:store)
299
+
300
+ subject.get_features_for_group(group)
301
+ end
302
+ end
303
+
304
+ context 'bandiera is having some problems' do
305
+ it 'returns a default response and logs a warning' do
306
+ stub_request(:get, url).to_return(status: 500, body: '')
307
+
308
+ expect(logger).to receive(:warn).once
309
+
310
+ response = subject.get_features_for_group(group)
311
+
312
+ expect(response).to be {}
313
+ end
314
+
315
+ it 'does not store anything in the cache' do
316
+ stub_request(:get, url).to_return(status: 500, body: '')
317
+
318
+ expect(subject.cache).to_not receive(:store)
319
+
320
+ subject.get_features_for_group(group)
321
+ end
322
+ end
323
+
324
+ context 'bandiera times out' do
325
+ it 'returns a default response and logs a warning' do
326
+ stub_request(:get, url).to_timeout
327
+
328
+ expect(logger).to receive(:warn).once
329
+
330
+ response = subject.get_features_for_group(group)
331
+
332
+ expect(response).to be {}
333
+ end
334
+
335
+ it 'does not store anything in the cache' do
336
+ stub_request(:get, url).to_timeout
337
+
338
+ expect(subject.cache).to_not receive(:store)
339
+
340
+ subject.get_features_for_group(group)
341
+ end
342
+ end
343
+ end
344
+
345
+ describe '#get_all' do
346
+ let(:url) { "#{api_uri}/v2/all" }
347
+
348
+ context 'all is ok' do
349
+ it 'returns the bandiera response' do
350
+ feature_hash = { 'pubserv' => { 'show-stuff' => true, 'show-other-stuff' => false } }
351
+ stub = stub_api_request(url, 'response' => feature_hash)
352
+ response = subject.get_all
353
+
354
+ expect(response).to eq(feature_hash)
355
+ expect(stub).to have_been_requested
356
+ end
357
+
358
+ it 'stores the feature values in the cache' do
359
+ feature_hash = { 'pubserv' => { 'show-stuff' => true, 'show-other-stuff' => false } }
360
+ stub_api_request(url, 'response' => feature_hash)
361
+
362
+ subject.get_all
363
+
364
+ cache_key = subject.send(:build_cache_key, 'pubserv', 'show-stuff', {})
365
+ expect(subject.cache.key?(cache_key)).to be true
366
+
367
+ cache_key = subject.send(:build_cache_key, 'pubserv', 'show-other-stuff', {})
368
+ expect(subject.cache.key?(cache_key)).to be true
369
+ end
370
+
371
+ context 'and the user has passed through some extra params' do
372
+ it 'passes them through to the API' do
373
+ stub = stub_request(:get, url)
374
+ .with(query: { user_group: 'admin', user_id: '12345' })
375
+ .to_return(
376
+ body: JSON.generate('response' => {}),
377
+ headers: { 'Content-Type' => 'application/json' }
378
+ )
379
+
380
+ subject.get_all({ user_group: 'admin', user_id: 12345 })
381
+
382
+ expect(stub).to have_been_requested
383
+ end
384
+ end
385
+
386
+ context 'but bandiera returns a warning along with the response' do
387
+ it 'logs the warning' do
388
+ stub = stub_api_request(url, 'response' => {}, 'warning' => 'The group does not exist')
389
+
390
+ expect(logger).to receive(:warn).once
391
+
392
+ response = subject.get_all
393
+
394
+ expect(response).to be {}
395
+ expect(stub).to have_been_requested
396
+ end
397
+ end
398
+ end
399
+
400
+ context 'bandiera is down' do
401
+ it 'returns a default response and logs a warning' do
402
+ stub_request(:get, url).to_return(status: [0, ''])
403
+
404
+ expect(logger).to receive(:warn).once
405
+
406
+ response = subject.get_all
407
+
408
+ expect(response).to be {}
409
+ end
410
+
411
+ it 'does not store anything in the cache' do
412
+ stub_request(:get, url).to_return(status: [0, ''])
413
+
414
+ expect(subject.cache).to_not receive(:store)
415
+
416
+ subject.get_all
417
+ end
418
+ end
419
+
420
+ context 'bandiera is having some problems' do
421
+ it 'returns a default response and logs a warning' do
422
+ stub_request(:get, url).to_return(status: 200, body: '<html></html>')
423
+
424
+ expect(logger).to receive(:warn).once
425
+
426
+ response = subject.get_all
427
+
428
+ expect(response).to be {}
429
+ end
430
+
431
+ it 'does not store anything in the cache' do
432
+ stub_request(:get, url).to_return(status: 500, body: '')
433
+
434
+ expect(subject.cache).to_not receive(:store)
435
+
436
+ subject.get_all
437
+ end
438
+ end
439
+
440
+ context 'bandiera times out' do
441
+ it 'returns a default response and logs a warning' do
442
+ stub_request(:get, url).to_timeout
443
+
444
+ expect(logger).to receive(:warn).once
445
+
446
+ response = subject.get_all
447
+
448
+ expect(response).to be {}
449
+ end
450
+
451
+ it 'does not store anything in the cache' do
452
+ stub_request(:get, url).to_timeout
453
+
454
+ expect(subject.cache).to_not receive(:store)
455
+
456
+ subject.get_all
457
+ end
458
+ end
459
+ end
460
+
461
+ private
462
+
463
+ def stub_api_request(url, response, headers = {})
464
+ headers.merge! 'User-Agent' => "Bandiera Ruby Client / #{Bandiera::Client::VERSION}"
465
+ stub_request(:get, url)
466
+ .with(headers: headers)
467
+ .to_return(
468
+ body: JSON.generate(response),
469
+ headers: { 'Content-Type' => 'application/json' }
470
+ )
471
+ end
472
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift File.join(__FILE__, '../../lib')
2
+
3
+ require 'bundler'
4
+ Bundler.setup(:default, :test)
5
+
6
+ ENV['RACK_ENV'] = 'test'
7
+
8
+ require 'macmillan/utils/rspec/rspec_defaults'
9
+ require 'macmillan/utils/rspec/webmock_helper'
10
+ require 'macmillan/utils/test_helpers/codeclimate_helper'
11
+ require 'macmillan/utils/test_helpers/simplecov_helper'
12
+ require 'pry'
13
+
14
+ require 'bandiera/client'
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bandiera-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Macmillan Science and Education (New Publsihing Platforms)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rest-client
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: moneta
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: macmillan-utils
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Bandiera is a simple, stand-alone feature flagging service that is not
126
+ tied to any existing web framework or language. This is a client for talking to
127
+ the web service.
128
+ email:
129
+ - npp-developers@macmillan.com
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - ".gitignore"
135
+ - ".hound.yml"
136
+ - ".rspec"
137
+ - ".rubocop.yml"
138
+ - Gemfile
139
+ - HISTORY
140
+ - LICENSE.txt
141
+ - README.md
142
+ - Rakefile
143
+ - bandiera-client.gemspec
144
+ - lib/bandiera/client.rb
145
+ - lib/bandiera/client/version.rb
146
+ - spec/lib/bandiera/client_spec.rb
147
+ - spec/spec_helper.rb
148
+ homepage: https://github.com/nature/bandiera-client-ruby
149
+ licenses:
150
+ - GPL-3
151
+ metadata: {}
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubyforge_project:
168
+ rubygems_version: 2.4.5
169
+ signing_key:
170
+ specification_version: 4
171
+ summary: Simple feature flagging API client.
172
+ test_files: []
173
+ has_rdoc: