flipper-api 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ae54a392fa555bc9ee34b7783aff686a17bc9543
4
- data.tar.gz: 21c6d2a34ed3789aaea62c84bb86a3558459716c
3
+ metadata.gz: 1fe848186acddf2d53ab3068d5f2ec2246ab688c
4
+ data.tar.gz: 20c08ae092d4b61c9a1412778a55f84ca9602c1b
5
5
  SHA512:
6
- metadata.gz: 09ce98c6e834ac45e3b46c4d47936f1cefe5e45facb26ccc1e2e183f45748d32190c7ee75f9cccb37ca3a11b0872af36af9b7f8360c7fa5463f248883d97f6f3
7
- data.tar.gz: 1c553a7e7f0acfd12e158f8211ae79b72941317093201e2d73dce235eaa4660e5cba4c108f661bb48c93d834880cdf6f15af4776de8ad1447d31486332296d85
6
+ metadata.gz: 8e7e1c899f4a5cd09ac3e129e8e83d0085a46b1ccb4bd05a6d849ad4ccc7a4a158313a08d22201d644f4d35820d70482b248e1a81283f5434ccfc93e70b88863
7
+ data.tar.gz: 4d367bab12c168329609aa4c9cac1b7098bd68d428d49d927c4c5ee22584864482877866a49d733c9a36fa4874d698b632277cff90ee027f800a0196343febbd
@@ -1,5 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require File.expand_path('../lib/flipper/version', __FILE__)
3
+ require File.expand_path('../lib/flipper/metadata', __FILE__)
3
4
 
4
5
  flipper_api_files = lambda do |file|
5
6
  file =~ %r{(flipper)[\/-]api}
@@ -17,6 +18,7 @@ Gem::Specification.new do |gem|
17
18
  gem.name = 'flipper-api'
18
19
  gem.require_paths = ['lib']
19
20
  gem.version = Flipper::VERSION
21
+ gem.metadata = Flipper::METADATA
20
22
 
21
23
  gem.add_dependency 'rack', '>= 1.4', '< 3'
22
24
  gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
@@ -1,7 +1,5 @@
1
1
  require 'rack'
2
2
  require 'flipper'
3
- require 'flipper/middleware/setup_env'
4
- require 'flipper/middleware/memoizer'
5
3
  require 'flipper/api/middleware'
6
4
  require 'flipper/api/json_params'
7
5
 
@@ -6,6 +6,16 @@ require 'json'
6
6
  module Flipper
7
7
  module Api
8
8
  class Action
9
+ module FeatureNameFromRoute
10
+ def feature_name
11
+ @feature_name ||= begin
12
+ match = request.path_info.match(self.class.route_regex)
13
+ match ? match[:feature_name] : nil
14
+ end
15
+ end
16
+ private :feature_name
17
+ end
18
+
9
19
  extend Forwardable
10
20
 
11
21
  VALID_REQUEST_METHOD_NAMES = Set.new([
@@ -21,7 +31,17 @@ module Flipper
21
31
  #
22
32
  # Returns nothing.
23
33
  def self.route(regex)
24
- @regex = regex
34
+ @route_regex = regex
35
+ end
36
+
37
+ # Internal: Does this action's route match the path.
38
+ def self.route_match?(path)
39
+ path.match(route_regex)
40
+ end
41
+
42
+ # Internal: The regex that matches which routes this action will work for.
43
+ def self.route_regex
44
+ @route_regex || raise("#{name}.route is not set")
25
45
  end
26
46
 
27
47
  # Internal: Initializes and runs an action for a given request.
@@ -34,11 +54,6 @@ module Flipper
34
54
  new(flipper, request).run
35
55
  end
36
56
 
37
- # Internal: The regex that matches which routes this action will work for.
38
- def self.regex
39
- @regex || raise("#{name}.route is not set")
40
- end
41
-
42
57
  # Public: The instance of the Flipper::DSL the middleware was
43
58
  # initialized with.
44
59
  attr_reader :flipper
@@ -1,5 +1,6 @@
1
1
  module Flipper
2
2
  module Api
3
+ # Internal: Used to detect the action that should be used in the middleware.
3
4
  class ActionCollection
4
5
  def initialize
5
6
  @action_classes = []
@@ -11,7 +12,7 @@ module Flipper
11
12
 
12
13
  def action_for_request(request)
13
14
  @action_classes.detect do |action_class|
14
- request.path_info =~ action_class.regex
15
+ action_class.route_match?(request.path_info)
15
16
  end
16
17
  end
17
18
  end
@@ -6,7 +6,9 @@ module Flipper
6
6
  module V1
7
7
  module Actions
8
8
  class ActorsGate < Api::Action
9
- route %r{features/[^/]*/actors/?\Z}
9
+ include FeatureNameFromRoute
10
+
11
+ route %r{\A/features/(?<feature_name>.*)/actors/?\Z}
10
12
 
11
13
  def post
12
14
  ensure_valid_params
@@ -32,10 +34,6 @@ module Flipper
32
34
  json_error_response(:flipper_id_invalid) if flipper_id.nil?
33
35
  end
34
36
 
35
- def feature_name
36
- @feature_name ||= Rack::Utils.unescape(path_parts[-2])
37
- end
38
-
39
37
  def flipper_id
40
38
  @flipper_id ||= params['flipper_id']
41
39
  end
@@ -6,10 +6,11 @@ module Flipper
6
6
  module V1
7
7
  module Actions
8
8
  class BooleanGate < Api::Action
9
- route %r{features/[^/]*/boolean/?\Z}
9
+ include FeatureNameFromRoute
10
+
11
+ route %r{\A/features/(?<feature_name>.*)/boolean/?\Z}
10
12
 
11
13
  def post
12
- feature_name = Rack::Utils.unescape(path_parts[-2])
13
14
  feature = flipper[feature_name]
14
15
  feature.enable
15
16
  decorated_feature = Decorators::Feature.new(feature)
@@ -17,7 +18,6 @@ module Flipper
17
18
  end
18
19
 
19
20
  def delete
20
- feature_name = Rack::Utils.unescape(path_parts[-2])
21
21
  feature = flipper[feature_name.to_sym]
22
22
  feature.disable
23
23
  decorated_feature = Decorators::Feature.new(feature)
@@ -6,10 +6,11 @@ module Flipper
6
6
  module V1
7
7
  module Actions
8
8
  class ClearFeature < Api::Action
9
- route %r{features/[^/]*/clear/?\Z}
9
+ include FeatureNameFromRoute
10
+
11
+ route %r{\A/features/(?<feature_name>.*)/clear/?\Z}
10
12
 
11
13
  def delete
12
- feature_name = Rack::Utils.unescape(path_parts[-2])
13
14
  feature = flipper[feature_name]
14
15
  feature.clear
15
16
  json_response({}, 204)
@@ -6,7 +6,9 @@ module Flipper
6
6
  module V1
7
7
  module Actions
8
8
  class Feature < Api::Action
9
- route %r{features/[^/]*/?\Z}
9
+ include FeatureNameFromRoute
10
+
11
+ route %r{\A/features/(?<feature_name>.*)/?\Z}
10
12
 
11
13
  def get
12
14
  return json_error_response(:feature_not_found) unless feature_exists?(feature_name)
@@ -21,10 +23,6 @@ module Flipper
21
23
 
22
24
  private
23
25
 
24
- def feature_name
25
- @feature_name ||= Rack::Utils.unescape(path_parts.last)
26
- end
27
-
28
26
  def feature_exists?(feature_name)
29
27
  flipper.features.map(&:key).include?(feature_name)
30
28
  end
@@ -6,7 +6,7 @@ module Flipper
6
6
  module V1
7
7
  module Actions
8
8
  class Features < Api::Action
9
- route(/features\Z/)
9
+ route %r{\A/features/?\Z}
10
10
 
11
11
  def get
12
12
  keys = params['keys']
@@ -6,7 +6,9 @@ module Flipper
6
6
  module V1
7
7
  module Actions
8
8
  class GroupsGate < Api::Action
9
- route %r{features/[^/]*/groups/?\Z}
9
+ include FeatureNameFromRoute
10
+
11
+ route %r{\A/features/(?<feature_name>.*)/groups/?\Z}
10
12
 
11
13
  def post
12
14
  ensure_valid_params
@@ -46,10 +48,6 @@ module Flipper
46
48
  !allow_unregistered_groups?
47
49
  end
48
50
 
49
- def feature_name
50
- @feature_name ||= Rack::Utils.unescape(path_parts[-2])
51
- end
52
-
53
51
  def group_name
54
52
  @group_name ||= params['name']
55
53
  end
@@ -6,7 +6,9 @@ module Flipper
6
6
  module V1
7
7
  module Actions
8
8
  class PercentageOfActorsGate < Api::Action
9
- route %r{features/[^/]*/percentage_of_actors/?\Z}
9
+ include FeatureNameFromRoute
10
+
11
+ route %r{\A/features/(?<feature_name>.*)/percentage_of_actors/?\Z}
10
12
 
11
13
  def post
12
14
  if percentage < 0 || percentage > 100
@@ -28,10 +30,6 @@ module Flipper
28
30
 
29
31
  private
30
32
 
31
- def feature_name
32
- @feature_name ||= Rack::Utils.unescape(path_parts[-2])
33
- end
34
-
35
33
  def percentage
36
34
  @percentage ||= begin
37
35
  Integer(params['percentage'])
@@ -6,7 +6,9 @@ module Flipper
6
6
  module V1
7
7
  module Actions
8
8
  class PercentageOfTimeGate < Api::Action
9
- route %r{features/[^/]*/percentage_of_time/?\Z}
9
+ include FeatureNameFromRoute
10
+
11
+ route %r{\A/features/(?<feature_name>.*)/percentage_of_time/?\Z}
10
12
 
11
13
  def post
12
14
  if percentage < 0 || percentage > 100
@@ -29,10 +31,6 @@ module Flipper
29
31
 
30
32
  private
31
33
 
32
- def feature_name
33
- @feature_name ||= Rack::Utils.unescape(path_parts[-2])
34
- end
35
-
36
34
  def percentage
37
35
  @percentage ||= begin
38
36
  Integer(params['percentage'])
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.15.0'.freeze
2
+ VERSION = '0.16.0'.freeze
3
3
  end
@@ -42,6 +42,24 @@ RSpec.describe Flipper::Api::V1::Actions::ActorsGate do
42
42
  end
43
43
  end
44
44
 
45
+ describe 'enable feature with slash in name' do
46
+ before do
47
+ flipper[:my_feature].disable_actor(actor)
48
+ post '/features/my/feature/actors', flipper_id: actor.flipper_id
49
+ end
50
+
51
+ it 'enables feature for actor' do
52
+ expect(last_response.status).to eq(200)
53
+ expect(flipper["my/feature"].enabled?(actor)).to be_truthy
54
+ expect(flipper["my/feature"].enabled_gate_names).to eq([:actor])
55
+ end
56
+
57
+ it 'returns decorated feature with actor enabled' do
58
+ gate = json_response['gates'].find { |gate| gate['key'] == 'actors' }
59
+ expect(gate['value']).to eq(['1'])
60
+ end
61
+ end
62
+
45
63
  describe 'enable missing flipper_id parameter' do
46
64
  before do
47
65
  flipper[:my_feature].enable
@@ -20,6 +20,23 @@ RSpec.describe Flipper::Api::V1::Actions::BooleanGate do
20
20
  end
21
21
  end
22
22
 
23
+ describe 'enable feature with slash in name' do
24
+ before do
25
+ flipper["my/feature"].disable
26
+ post '/features/my/feature/boolean'
27
+ end
28
+
29
+ it 'enables feature' do
30
+ expect(last_response.status).to eq(200)
31
+ expect(flipper["my/feature"].on?).to be_truthy
32
+ end
33
+
34
+ it 'returns decorated feature with boolean gate enabled' do
35
+ boolean_gate = json_response['gates'].find { |gate| gate['key'] == 'boolean' }
36
+ expect(boolean_gate['value']).to be_truthy
37
+ end
38
+ end
39
+
23
40
  describe 'disable' do
24
41
  before do
25
42
  flipper[:my_feature].enable
@@ -23,4 +23,25 @@ RSpec.describe Flipper::Api::V1::Actions::ClearFeature do
23
23
  expect(flipper[:my_feature].off?).to be_truthy
24
24
  end
25
25
  end
26
+
27
+ describe 'clear feature with slash in name' do
28
+ before do
29
+ Flipper.register(:admins) {}
30
+ actor22 = Flipper::Actor.new('22')
31
+
32
+ feature = flipper["my/feature"]
33
+ feature.enable flipper.boolean
34
+ feature.enable flipper.group(:admins)
35
+ feature.enable flipper.actor(actor22)
36
+ feature.enable flipper.actors(25)
37
+ feature.enable flipper.time(45)
38
+
39
+ delete '/features/my/feature/clear'
40
+ end
41
+
42
+ it 'clears feature' do
43
+ expect(last_response.status).to eq(204)
44
+ expect(flipper["my/feature"].off?).to be_truthy
45
+ end
46
+ end
26
47
  end
@@ -22,11 +22,6 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
22
22
  'name' => 'boolean',
23
23
  'value' => 'true',
24
24
  },
25
- {
26
- 'key' => 'groups',
27
- 'name' => 'group',
28
- 'value' => [],
29
- },
30
25
  {
31
26
  'key' => 'actors',
32
27
  'name' => 'actor',
@@ -42,6 +37,11 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
42
37
  'name' => 'percentage_of_time',
43
38
  'value' => nil,
44
39
  },
40
+ {
41
+ 'key' => 'groups',
42
+ 'name' => 'group',
43
+ 'value' => [],
44
+ },
45
45
  ],
46
46
  }
47
47
 
@@ -66,11 +66,6 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
66
66
  'name' => 'boolean',
67
67
  'value' => nil,
68
68
  },
69
- {
70
- 'key' => 'groups',
71
- 'name' => 'group',
72
- 'value' => [],
73
- },
74
69
  {
75
70
  'key' => 'actors',
76
71
  'name' => 'actor',
@@ -86,6 +81,11 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
86
81
  'name' => 'percentage_of_time',
87
82
  'value' => nil,
88
83
  },
84
+ {
85
+ 'key' => 'groups',
86
+ 'name' => 'group',
87
+ 'value' => [],
88
+ },
89
89
  ],
90
90
  }
91
91
 
@@ -126,11 +126,50 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
126
126
  'name' => 'boolean',
127
127
  'value' => 'true',
128
128
  },
129
+ {
130
+ 'key' => 'actors',
131
+ 'name' => 'actor',
132
+ 'value' => [],
133
+ },
134
+ {
135
+ 'key' => 'percentage_of_actors',
136
+ 'name' => 'percentage_of_actors',
137
+ 'value' => nil,
138
+ },
139
+ {
140
+ 'key' => 'percentage_of_time',
141
+ 'name' => 'percentage_of_time',
142
+ 'value' => nil,
143
+ },
129
144
  {
130
145
  'key' => 'groups',
131
146
  'name' => 'group',
132
147
  'value' => [],
133
148
  },
149
+ ],
150
+ }
151
+
152
+ expect(last_response.status).to eq(200)
153
+ expect(json_response).to eq(response_body)
154
+ end
155
+ end
156
+
157
+ context 'feature with name that has slash' do
158
+ before do
159
+ flipper["my/feature"].enable
160
+ get '/features/my/feature'
161
+ end
162
+
163
+ it 'responds with correct attributes' do
164
+ response_body = {
165
+ 'key' => 'my/feature',
166
+ 'state' => 'on',
167
+ 'gates' => [
168
+ {
169
+ 'key' => 'boolean',
170
+ 'name' => 'boolean',
171
+ 'value' => 'true',
172
+ },
134
173
  {
135
174
  'key' => 'actors',
136
175
  'name' => 'actor',
@@ -146,6 +185,11 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
146
185
  'name' => 'percentage_of_time',
147
186
  'value' => nil,
148
187
  },
188
+ {
189
+ 'key' => 'groups',
190
+ 'name' => 'group',
191
+ 'value' => [],
192
+ },
149
193
  ],
150
194
  }
151
195
 
@@ -25,11 +25,6 @@ RSpec.describe Flipper::Api::V1::Actions::Features do
25
25
  'name' => 'boolean',
26
26
  'value' => 'true',
27
27
  },
28
- {
29
- 'key' => 'groups',
30
- 'name' => 'group',
31
- 'value' => [],
32
- },
33
28
  {
34
29
  'key' => 'actors',
35
30
  'name' => 'actor',
@@ -45,6 +40,11 @@ RSpec.describe Flipper::Api::V1::Actions::Features do
45
40
  'name' => 'percentage_of_time',
46
41
  'value' => nil,
47
42
  },
43
+ {
44
+ 'key' => 'groups',
45
+ 'name' => 'group',
46
+ 'value' => [],
47
+ },
48
48
  ],
49
49
  },
50
50
  ],
@@ -120,11 +120,6 @@ RSpec.describe Flipper::Api::V1::Actions::Features do
120
120
  'name' => 'boolean',
121
121
  'value' => nil,
122
122
  },
123
- {
124
- 'key' => 'groups',
125
- 'name' => 'group',
126
- 'value' => [],
127
- },
128
123
  {
129
124
  'key' => 'actors',
130
125
  'name' => 'actor',
@@ -140,6 +135,11 @@ RSpec.describe Flipper::Api::V1::Actions::Features do
140
135
  'name' => 'percentage_of_time',
141
136
  'value' => nil,
142
137
  },
138
+ {
139
+ 'key' => 'groups',
140
+ 'name' => 'group',
141
+ 'value' => [],
142
+ },
143
143
  ],
144
144
  }
145
145
  expect(json_response).to eq(expected_response)
@@ -26,6 +26,29 @@ RSpec.describe Flipper::Api::V1::Actions::GroupsGate do
26
26
  end
27
27
  end
28
28
 
29
+ describe 'enable feature with slash in name' do
30
+ before do
31
+ flipper["my/feature"].disable
32
+ Flipper.register(:admins) do |actor|
33
+ actor.respond_to?(:admin?) && actor.admin?
34
+ end
35
+ post '/features/my/feature/groups', name: 'admins'
36
+ end
37
+
38
+ it 'enables feature for group' do
39
+ person = double
40
+ allow(person).to receive(:flipper_id).and_return(1)
41
+ allow(person).to receive(:admin?).and_return(true)
42
+ expect(last_response.status).to eq(200)
43
+ expect(flipper["my/feature"].enabled?(person)).to be_truthy
44
+ end
45
+
46
+ it 'returns decorated feature with group enabled' do
47
+ group_gate = json_response['gates'].find { |m| m['name'] == 'group' }
48
+ expect(group_gate['value']).to eq(['admins'])
49
+ end
50
+ end
51
+
29
52
  describe 'enable without name params' do
30
53
  before do
31
54
  flipper[:my_feature].disable
@@ -4,6 +4,22 @@ RSpec.describe Flipper::Api::V1::Actions::PercentageOfActorsGate do
4
4
  let(:app) { build_api(flipper) }
5
5
 
6
6
  describe 'enable' do
7
+ context 'for feature with slash in name' do
8
+ before do
9
+ flipper["my/feature"].disable
10
+ post '/features/my/feature/percentage_of_actors', percentage: '10'
11
+ end
12
+
13
+ it 'enables gate for feature' do
14
+ expect(flipper["my/feature"].enabled_gate_names).to include(:percentage_of_actors)
15
+ end
16
+
17
+ it 'returns decorated feature with gate enabled for 10 percent of actors' do
18
+ gate = json_response['gates'].find { |gate| gate['name'] == 'percentage_of_actors' }
19
+ expect(gate['value']).to eq('10')
20
+ end
21
+ end
22
+
7
23
  context 'url-encoded request' do
8
24
  before do
9
25
  flipper[:my_feature].disable
@@ -19,6 +19,22 @@ RSpec.describe Flipper::Api::V1::Actions::PercentageOfTimeGate do
19
19
  end
20
20
  end
21
21
 
22
+ describe 'enable for feature with slash in name' do
23
+ before do
24
+ flipper["my/feature"].disable
25
+ post '/features/my/feature/percentage_of_time', percentage: '10'
26
+ end
27
+
28
+ it 'enables gate for feature' do
29
+ expect(flipper["my/feature"].enabled_gate_names).to include(:percentage_of_time)
30
+ end
31
+
32
+ it 'returns decorated feature with gate enabled for 5% of time' do
33
+ gate = json_response['gates'].find { |gate| gate['name'] == 'percentage_of_time' }
34
+ expect(gate['value']).to eq('10')
35
+ end
36
+ end
37
+
22
38
  describe 'disable without percentage' do
23
39
  before do
24
40
  flipper[:my_feature].enable_percentage_of_time(10)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-12 00:00:00.000000000 Z
11
+ date: 2018-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.15.0
39
+ version: 0.16.0
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.15.0
46
+ version: 0.16.0
47
47
  description: Rack middleware that provides an API for the flipper gem.
48
48
  email:
49
49
  - nunemaker@gmail.com
@@ -85,7 +85,8 @@ files:
85
85
  homepage: https://github.com/jnunemaker/flipper
86
86
  licenses:
87
87
  - MIT
88
- metadata: {}
88
+ metadata:
89
+ changelog_uri: https://github.com/jnunemaker/flipper/blob/master/Changelog.md
89
90
  post_install_message:
90
91
  rdoc_options: []
91
92
  require_paths: