Skip to content

Commit

Permalink
Add support for slashes in feature names to API
Browse files Browse the repository at this point in the history
refs #357
refs 3033e3c
  • Loading branch information
jnunemaker committed Jun 3, 2018
1 parent 569d8c3 commit 1602d2e
Show file tree
Hide file tree
Showing 17 changed files with 221 additions and 20 deletions.
14 changes: 11 additions & 3 deletions lib/flipper/api/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ class Action

# Public: Call this in subclasses so the action knows its route.
#
# regex - The Regexp that this action should run for.
# block - The Block that determines if this can handle a given request.
#
# Returns nothing.
def self.route(regex)
@regex = regex
def self.match(&block)
if block_given?
@match = block
else
@match || raise("No match block set for #{name}")
end
end

def self.match?(request)
!!match.call(request)
end

# Internal: Initializes and runs an action for a given request.
Expand Down
3 changes: 2 additions & 1 deletion lib/flipper/api/action_collection.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Flipper
module Api
# Internal: Used to detect the action that should be used in the middleware.
class ActionCollection
def initialize
@action_classes = []
Expand All @@ -11,7 +12,7 @@ def add(action_class)

def action_for_request(request)
@action_classes.detect do |action_class|
request.path_info =~ action_class.regex
action_class.match?(request)
end
end
end
Expand Down
8 changes: 6 additions & 2 deletions lib/flipper/api/v1/actions/actors_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module Api
module V1
module Actions
class ActorsGate < Api::Action
route %r{features/[^/]*/actors/?\Z}
REGEX = %r{\A/features/(.*)/actors/?\Z}
match { |request| request.path_info =~ REGEX }

def post
ensure_valid_params
Expand All @@ -33,7 +34,10 @@ def ensure_valid_params
end

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts[-2])
@feature_name ||= begin
match = request.path_info.match(REGEX)
match ? match[1] : nil
end
end

def flipper_id
Expand Down
14 changes: 11 additions & 3 deletions lib/flipper/api/v1/actions/boolean_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,31 @@ module Api
module V1
module Actions
class BooleanGate < Api::Action
route %r{features/[^/]*/boolean/?\Z}
REGEX = %r{\A/features/(.*)/boolean/?\Z}
match { |request| request.path_info =~ REGEX }

def post
feature_name = Rack::Utils.unescape(path_parts[-2])
feature = flipper[feature_name]
feature.enable
decorated_feature = Decorators::Feature.new(feature)
json_response(decorated_feature.as_json, 200)
end

def delete
feature_name = Rack::Utils.unescape(path_parts[-2])
feature = flipper[feature_name.to_sym]
feature.disable
decorated_feature = Decorators::Feature.new(feature)
json_response(decorated_feature.as_json, 200)
end

private

def feature_name
@feature_name ||= begin
match = request.path_info.match(REGEX)
match ? match[1] : nil
end
end
end
end
end
Expand Down
13 changes: 11 additions & 2 deletions lib/flipper/api/v1/actions/clear_feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ module Api
module V1
module Actions
class ClearFeature < Api::Action
route %r{features/[^/]*/clear/?\Z}
REGEX = %r{\A/features/(.*)/clear/?\Z}
match { |request| request.path_info =~ REGEX }

def delete
feature_name = Rack::Utils.unescape(path_parts[-2])
feature = flipper[feature_name]
feature.clear
json_response({}, 204)
end

private

def feature_name
@feature_name ||= begin
match = request.path_info.match(REGEX)
match ? match[1] : nil
end
end
end
end
end
Expand Down
8 changes: 6 additions & 2 deletions lib/flipper/api/v1/actions/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module Api
module V1
module Actions
class Feature < Api::Action
route %r{features/[^/]*/?\Z}
REGEX = %r{\A/features/(.*)/?\Z}
match { |request| request.path_info =~ REGEX }

def get
return json_error_response(:feature_not_found) unless feature_exists?(feature_name)
Expand All @@ -22,7 +23,10 @@ def delete
private

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts.last)
@feature_name ||= begin
match = request.path_info.match(REGEX)
match ? match[1] : nil
end
end

def feature_exists?(feature_name)
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/api/v1/actions/features.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Api
module V1
module Actions
class Features < Api::Action
route(/features\Z/)
match { |request| request.path_info =~ %r{\A/features/?\Z} }

def get
keys = params['keys']
Expand Down
8 changes: 6 additions & 2 deletions lib/flipper/api/v1/actions/groups_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module Api
module V1
module Actions
class GroupsGate < Api::Action
route %r{features/[^/]*/groups/?\Z}
REGEX = %r{\A/features/(.*)/groups/?\Z}
match { |request| request.path_info =~ REGEX }

def post
ensure_valid_params
Expand Down Expand Up @@ -47,7 +48,10 @@ def disallow_unregistered_groups?
end

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts[-2])
@feature_name ||= begin
match = request.path_info.match(REGEX)
match ? match[1] : nil
end
end

def group_name
Expand Down
8 changes: 6 additions & 2 deletions lib/flipper/api/v1/actions/percentage_of_actors_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module Api
module V1
module Actions
class PercentageOfActorsGate < Api::Action
route %r{features/[^/]*/percentage_of_actors/?\Z}
REGEX = %r{\A/features/(.*)/percentage_of_actors/?\Z}
match { |request| request.path_info =~ REGEX }

def post
if percentage < 0 || percentage > 100
Expand All @@ -29,7 +30,10 @@ def delete
private

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts[-2])
@feature_name ||= begin
match = request.path_info.match(REGEX)
match ? match[1] : nil
end
end

def percentage
Expand Down
8 changes: 6 additions & 2 deletions lib/flipper/api/v1/actions/percentage_of_time_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module Api
module V1
module Actions
class PercentageOfTimeGate < Api::Action
route %r{features/[^/]*/percentage_of_time/?\Z}
REGEX = %r{\A/features/(.*)/percentage_of_time/?\Z}
match { |request| request.path_info =~ REGEX }

def post
if percentage < 0 || percentage > 100
Expand All @@ -30,7 +31,10 @@ def delete
private

def feature_name
@feature_name ||= Rack::Utils.unescape(path_parts[-2])
@feature_name ||= begin
match = request.path_info.match(REGEX)
match ? match[1] : nil
end
end

def percentage
Expand Down
18 changes: 18 additions & 0 deletions spec/flipper/api/v1/actions/actors_gate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@
end
end

describe 'enable feature with slash in name' do
before do
flipper[:my_feature].disable_actor(actor)
post '/features/my/feature/actors', flipper_id: actor.flipper_id
end

it 'enables feature for actor' do
expect(last_response.status).to eq(200)
expect(flipper["my/feature"].enabled?(actor)).to be_truthy
expect(flipper["my/feature"].enabled_gate_names).to eq([:actor])
end

it 'returns decorated feature with actor enabled' do
gate = json_response['gates'].find { |gate| gate['key'] == 'actors' }
expect(gate['value']).to eq(['1'])
end
end

describe 'enable missing flipper_id parameter' do
before do
flipper[:my_feature].enable
Expand Down
17 changes: 17 additions & 0 deletions spec/flipper/api/v1/actions/boolean_gate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@
end
end

describe 'enable feature with slash in name' do
before do
flipper["my/feature"].disable
post '/features/my/feature/boolean'
end

it 'enables feature' do
expect(last_response.status).to eq(200)
expect(flipper["my/feature"].on?).to be_truthy
end

it 'returns decorated feature with boolean gate enabled' do
boolean_gate = json_response['gates'].find { |gate| gate['key'] == 'boolean' }
expect(boolean_gate['value']).to be_truthy
end
end

describe 'disable' do
before do
flipper[:my_feature].enable
Expand Down
21 changes: 21 additions & 0 deletions spec/flipper/api/v1/actions/clear_feature_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,25 @@
expect(flipper[:my_feature].off?).to be_truthy
end
end

describe 'clear feature with slash in name' do
before do
Flipper.register(:admins) {}
actor22 = Flipper::Actor.new('22')

feature = flipper["my/feature"]
feature.enable flipper.boolean
feature.enable flipper.group(:admins)
feature.enable flipper.actor(actor22)
feature.enable flipper.actors(25)
feature.enable flipper.time(45)

delete '/features/my/feature/clear'
end

it 'clears feature' do
expect(last_response.status).to eq(204)
expect(flipper["my/feature"].off?).to be_truthy
end
end
end
44 changes: 44 additions & 0 deletions spec/flipper/api/v1/actions/feature_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,50 @@
expect(json_response).to eq(response_body)
end
end

context 'feature with name that has slash' do
before do
flipper["my/feature"].enable
get '/features/my/feature'
end

it 'responds with correct attributes' do
response_body = {
'key' => 'my/feature',
'state' => 'on',
'gates' => [
{
'key' => 'boolean',
'name' => 'boolean',
'value' => 'true',
},
{
'key' => 'groups',
'name' => 'group',
'value' => [],
},
{
'key' => 'actors',
'name' => 'actor',
'value' => [],
},
{
'key' => 'percentage_of_actors',
'name' => 'percentage_of_actors',
'value' => nil,
},
{
'key' => 'percentage_of_time',
'name' => 'percentage_of_time',
'value' => nil,
},
],
}

expect(last_response.status).to eq(200)
expect(json_response).to eq(response_body)
end
end
end

describe 'delete' do
Expand Down
23 changes: 23 additions & 0 deletions spec/flipper/api/v1/actions/groups_gate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,29 @@
end
end

describe 'enable feature with slash in name' do
before do
flipper["my/feature"].disable
Flipper.register(:admins) do |actor|
actor.respond_to?(:admin?) && actor.admin?
end
post '/features/my/feature/groups', name: 'admins'
end

it 'enables feature for group' do
person = double
allow(person).to receive(:flipper_id).and_return(1)
allow(person).to receive(:admin?).and_return(true)
expect(last_response.status).to eq(200)
expect(flipper["my/feature"].enabled?(person)).to be_truthy
end

it 'returns decorated feature with group enabled' do
group_gate = json_response['gates'].find { |m| m['name'] == 'group' }
expect(group_gate['value']).to eq(['admins'])
end
end

describe 'enable without name params' do
before do
flipper[:my_feature].disable
Expand Down
16 changes: 16 additions & 0 deletions spec/flipper/api/v1/actions/percentage_of_actors_gate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
let(:app) { build_api(flipper) }

describe 'enable' do
context 'for feature with slash in name' do
before do
flipper["my/feature"].disable
post '/features/my/feature/percentage_of_actors', percentage: '10'
end

it 'enables gate for feature' do
expect(flipper["my/feature"].enabled_gate_names).to include(:percentage_of_actors)
end

it 'returns decorated feature with gate enabled for 10 percent of actors' do
gate = json_response['gates'].find { |gate| gate['name'] == 'percentage_of_actors' }
expect(gate['value']).to eq('10')
end
end

context 'url-encoded request' do
before do
flipper[:my_feature].disable
Expand Down
Loading

0 comments on commit 1602d2e

Please sign in to comment.