bandiera-client 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: