flipper-api 0.10.2 → 0.11.0.beta1

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/flipper-api.gemspec +12 -12
  3. data/lib/flipper/api/action.rb +12 -10
  4. data/lib/flipper/api/action_collection.rb +2 -2
  5. data/lib/flipper/api/error_response.rb +9 -8
  6. data/lib/flipper/api/json_params.rb +45 -0
  7. data/lib/flipper/api/middleware.rb +3 -28
  8. data/lib/flipper/api/v1/actions/actors_gate.rb +1 -9
  9. data/lib/flipper/api/v1/actions/boolean_gate.rb +1 -1
  10. data/lib/flipper/api/v1/actions/clear_feature.rb +21 -0
  11. data/lib/flipper/api/v1/actions/feature.rb +8 -16
  12. data/lib/flipper/api/v1/actions/features.rb +26 -8
  13. data/lib/flipper/api/v1/actions/groups_gate.rb +18 -7
  14. data/lib/flipper/api/v1/actions/percentage_of_actors_gate.rb +6 -24
  15. data/lib/flipper/api/v1/actions/percentage_of_time_gate.rb +6 -23
  16. data/lib/flipper/api/v1/decorators/feature.rb +3 -4
  17. data/lib/flipper/api.rb +11 -27
  18. data/lib/flipper/version.rb +1 -1
  19. data/spec/flipper/api/action_spec.rb +30 -36
  20. data/spec/flipper/api/json_params_spec.rb +81 -0
  21. data/spec/flipper/api/v1/actions/actors_gate_spec.rb +33 -22
  22. data/spec/flipper/api/v1/actions/boolean_gate_spec.rb +2 -2
  23. data/spec/flipper/api/v1/actions/clear_feature_spec.rb +27 -0
  24. data/spec/flipper/api/v1/actions/feature_spec.rb +28 -30
  25. data/spec/flipper/api/v1/actions/features_spec.rb +79 -44
  26. data/spec/flipper/api/v1/actions/groups_gate_spec.rb +92 -10
  27. data/spec/flipper/api/v1/actions/percentage_of_actors_gate_spec.rb +62 -24
  28. data/spec/flipper/api/v1/actions/percentage_of_time_gate_spec.rb +34 -15
  29. data/spec/flipper/api_spec.rb +55 -0
  30. metadata +15 -7
@@ -1,68 +1,66 @@
1
1
  require 'helper'
2
2
 
3
3
  RSpec.describe Flipper::Api::Action do
4
- let(:action_subclass) {
4
+ let(:action_subclass) do
5
5
  Class.new(described_class) do
6
6
  def noooope
7
- raise "should never run this"
7
+ raise 'should never run this'
8
8
  end
9
9
 
10
10
  def get
11
- [200, {}, "get"]
11
+ [200, {}, 'get']
12
12
  end
13
13
 
14
14
  def post
15
- [200, {}, "post"]
15
+ [200, {}, 'post']
16
16
  end
17
17
 
18
18
  def put
19
- [200, {}, "put"]
19
+ [200, {}, 'put']
20
20
  end
21
21
 
22
22
  def delete
23
- [200, {}, "delete"]
23
+ [200, {}, 'delete']
24
24
  end
25
25
  end
26
- }
26
+ end
27
27
 
28
28
  describe 'https verbs' do
29
-
30
29
  it "won't run method that isn't whitelisted" do
31
- fake_request = Struct.new(:request_method, :env, :session).new("NOOOOPE", {}, {})
30
+ fake_request = Struct.new(:request_method, :env, :session).new('NOOOOPE', {}, {})
32
31
  action = action_subclass.new(flipper, fake_request)
33
- expect {
32
+ expect do
34
33
  action.run
35
- }.to raise_error(Flipper::Api::RequestMethodNotSupported)
34
+ end.to raise_error(Flipper::Api::RequestMethodNotSupported)
36
35
  end
37
36
 
38
- it "will run get" do
39
- fake_request = Struct.new(:request_method, :env, :session).new("GET", {}, {})
37
+ it 'will run get' do
38
+ fake_request = Struct.new(:request_method, :env, :session).new('GET', {}, {})
40
39
  action = action_subclass.new(flipper, fake_request)
41
- expect(action.run).to eq([200, {}, "get"])
40
+ expect(action.run).to eq([200, {}, 'get'])
42
41
  end
43
42
 
44
- it "will run post" do
45
- fake_request = Struct.new(:request_method, :env, :session).new("POST", {}, {})
43
+ it 'will run post' do
44
+ fake_request = Struct.new(:request_method, :env, :session).new('POST', {}, {})
46
45
  action = action_subclass.new(flipper, fake_request)
47
- expect(action.run).to eq([200, {}, "post"])
46
+ expect(action.run).to eq([200, {}, 'post'])
48
47
  end
49
48
 
50
- it "will run put" do
51
- fake_request = Struct.new(:request_method, :env, :session).new("PUT", {}, {})
49
+ it 'will run put' do
50
+ fake_request = Struct.new(:request_method, :env, :session).new('PUT', {}, {})
52
51
  action = action_subclass.new(flipper, fake_request)
53
- expect(action.run).to eq([200, {}, "put"])
52
+ expect(action.run).to eq([200, {}, 'put'])
54
53
  end
55
54
 
56
- it "will run delete" do
57
- fake_request = Struct.new(:request_method, :env, :session).new("DELETE", {}, {})
55
+ it 'will run delete' do
56
+ fake_request = Struct.new(:request_method, :env, :session).new('DELETE', {}, {})
58
57
  action = action_subclass.new(flipper, fake_request)
59
- expect(action.run).to eq([200, {}, "delete"])
58
+ expect(action.run).to eq([200, {}, 'delete'])
60
59
  end
61
60
  end
62
61
 
63
62
  describe '#json_error_response' do
64
- describe ":feature_not_found" do
65
-
63
+ describe ':feature_not_found' do
66
64
  it 'locates and serializes error correctly' do
67
65
  action = action_subclass.new({}, {})
68
66
  response = catch(:halt) do
@@ -71,15 +69,12 @@ RSpec.describe Flipper::Api::Action do
71
69
  status, headers, body = response
72
70
  parsed_body = JSON.parse(body[0])
73
71
 
74
- expect(headers["Content-Type"]).to eq("application/json")
75
- expect(parsed_body["code"]).to eq(1)
76
- expect(parsed_body["message"]).to eq("Feature not found.")
77
- expect(parsed_body["more_info"]).to eq("https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference")
72
+ expect(headers['Content-Type']).to eq('application/json')
73
+ expect(parsed_body).to eql(api_not_found_response)
78
74
  end
79
75
  end
80
76
 
81
77
  describe ':group_not_registered' do
82
-
83
78
  it 'locates and serializes error correctly' do
84
79
  action = action_subclass.new({}, {})
85
80
  response = catch(:halt) do
@@ -88,19 +83,18 @@ RSpec.describe Flipper::Api::Action do
88
83
  status, headers, body = response
89
84
  parsed_body = JSON.parse(body[0])
90
85
 
91
- expect(headers["Content-Type"]).to eq("application/json")
92
- expect(parsed_body["code"]).to eq(2)
93
- expect(parsed_body["message"]).to eq("Group not registered.")
94
- expect(parsed_body["more_info"]).to eq("https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference")
86
+ expect(headers['Content-Type']).to eq('application/json')
87
+ expect(parsed_body['code']).to eq(2)
88
+ expect(parsed_body['message']).to eq('Group not registered.')
89
+ expect(parsed_body['more_info']).to eq(api_error_code_reference_url)
95
90
  end
96
91
  end
97
92
 
98
93
  describe 'invalid error key' do
99
-
100
94
  it 'raises descriptive error' do
101
95
  action = action_subclass.new({}, {})
102
96
  catch(:halt) do
103
- expect{ action.json_error_response(:invalid_error_key) }.to raise_error(KeyError)
97
+ expect { action.json_error_response(:invalid_error_key) }.to raise_error(KeyError)
104
98
  end
105
99
  end
106
100
  end
@@ -0,0 +1,81 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe Flipper::Api::JsonParams do
4
+ let(:app) do
5
+ app = lambda do |env|
6
+ request = Rack::Request.new(env)
7
+ [200, { 'Content-Type' => 'application/json' }, [JSON.generate(request.params)]]
8
+ end
9
+ builder = Rack::Builder.new
10
+ builder.use described_class
11
+ builder.run app
12
+ builder
13
+ end
14
+
15
+ describe 'json post request' do
16
+ it 'adds request body to params' do
17
+ response = post '/',
18
+ JSON.generate(flipper_id: 'user:2'),
19
+ 'CONTENT_TYPE' => 'application/json'
20
+
21
+ params = JSON.parse(response.body)
22
+ expect(params).to eq('flipper_id' => 'user:2')
23
+ end
24
+
25
+ it 'handles request bodies with multiple params' do
26
+ response = post '/',
27
+ JSON.generate(flipper_id: 'user:2', language: 'ruby'),
28
+ 'CONTENT_TYPE' => 'application/json'
29
+
30
+ params = JSON.parse(response.body)
31
+ expect(params).to eq('flipper_id' => 'user:2', 'language' => 'ruby')
32
+ end
33
+
34
+ it 'handles request bodies and single query string params' do
35
+ response = post '/?language=ruby',
36
+ JSON.generate(flipper_id: 'user:2'),
37
+ 'CONTENT_TYPE' => 'application/json'
38
+
39
+ params = JSON.parse(response.body)
40
+ expect(params).to eq('flipper_id' => 'user:2', 'language' => 'ruby')
41
+ end
42
+
43
+ it 'handles request bodies and multiple query string params' do
44
+ response = post '/?language=ruby&framework=rails',
45
+ JSON.generate(flipper_id: 'user:2'),
46
+ 'CONTENT_TYPE' => 'application/json'
47
+
48
+ params = JSON.parse(response.body)
49
+ expect(params).to eq('flipper_id' => 'user:2', 'language' => 'ruby', 'framework' => 'rails')
50
+ end
51
+
52
+ it 'favors request body params' do
53
+ response = post '/?language=javascript',
54
+ JSON.generate(flipper_id: 'user:2', language: 'ruby'),
55
+ 'CONTENT_TYPE' => 'application/json'
56
+
57
+ params = JSON.parse(response.body)
58
+ expect(params).to eq('flipper_id' => 'user:2', 'language' => 'ruby')
59
+ end
60
+ end
61
+
62
+ describe 'url-encoded request' do
63
+ it 'handles params the same as a json request' do
64
+ response = post '/', flipper_id: 'user:2'
65
+ params = JSON.parse(response.body)
66
+ expect(params).to eq('flipper_id' => 'user:2')
67
+ end
68
+
69
+ it 'handles single query string params' do
70
+ response = post '/?language=ruby', flipper_id: 'user:2'
71
+ params = JSON.parse(response.body)
72
+ expect(params).to eq('flipper_id' => 'user:2', 'language' => 'ruby')
73
+ end
74
+
75
+ it 'handles multiple query string params' do
76
+ response = post '/?language=ruby&framework=rails', flipper_id: 'user:2'
77
+ params = JSON.parse(response.body)
78
+ expect(params).to eq('flipper_id' => 'user:2', 'language' => 'ruby', 'framework' => 'rails')
79
+ end
80
+ end
81
+ end
@@ -2,13 +2,12 @@ require 'helper'
2
2
 
3
3
  RSpec.describe Flipper::Api::V1::Actions::ActorsGate do
4
4
  let(:app) { build_api(flipper) }
5
+ let(:actor) { Flipper::Api::Actor.new('1') }
5
6
 
6
7
  describe 'enable' do
7
- let(:actor) { Flipper::Api::Actor.new("1") }
8
-
9
8
  before do
10
9
  flipper[:my_feature].disable_actor(actor)
11
- post '/api/v1/features/my_feature/actors', { flipper_id: actor.flipper_id }
10
+ post '/features/my_feature/actors', flipper_id: actor.flipper_id
12
11
  end
13
12
 
14
13
  it 'enables feature for actor' do
@@ -19,16 +18,16 @@ RSpec.describe Flipper::Api::V1::Actions::ActorsGate do
19
18
 
20
19
  it 'returns decorated feature with actor enabled' do
21
20
  gate = json_response['gates'].find { |gate| gate['key'] == 'actors' }
22
- expect(gate['value']).to eq(["1"])
21
+ expect(gate['value']).to eq(['1'])
23
22
  end
24
23
  end
25
24
 
26
25
  describe 'disable' do
27
- let(:actor) { Flipper::Api::Actor.new("1") }
26
+ let(:actor) { Flipper::Api::Actor.new('1') }
28
27
 
29
28
  before do
30
29
  flipper[:my_feature].enable_actor(actor)
31
- delete '/api/v1/features/my_feature/actors', { flipper_id: actor.flipper_id }
30
+ delete '/features/my_feature/actors', flipper_id: actor.flipper_id
32
31
  end
33
32
 
34
33
  it 'disables feature' do
@@ -46,70 +45,82 @@ RSpec.describe Flipper::Api::V1::Actions::ActorsGate do
46
45
  describe 'enable missing flipper_id parameter' do
47
46
  before do
48
47
  flipper[:my_feature].enable
49
- post '/api/v1/features/my_feature/actors'
48
+ post '/features/my_feature/actors'
50
49
  end
51
50
 
52
51
  it 'returns correct error response' do
53
52
  expect(last_response.status).to eq(422)
54
- expect(json_response).to eq({ 'code' => 4, 'message' => 'Required parameter flipper_id is missing.', 'more_info' => 'https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference' })
53
+ expect(json_response).to eq(api_flipper_id_is_missing_response)
55
54
  end
56
55
  end
57
56
 
58
57
  describe 'disable missing flipper_id parameter' do
59
58
  before do
60
59
  flipper[:my_feature].enable
61
- delete '/api/v1/features/my_feature/actors'
60
+ delete '/features/my_feature/actors'
62
61
  end
63
62
 
64
63
  it 'returns correct error response' do
65
64
  expect(last_response.status).to eq(422)
66
- expect(json_response).to eq({ 'code' => 4, 'message' => 'Required parameter flipper_id is missing.', 'more_info' => 'https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference' })
65
+ expect(json_response).to eq(api_flipper_id_is_missing_response)
67
66
  end
68
67
  end
69
68
 
70
69
  describe 'enable nil flipper_id parameter' do
71
70
  before do
72
71
  flipper[:my_feature].enable
73
- post '/api/v1/features/my_feature/actors', { flipper_id: nil }
72
+ post '/features/my_feature/actors', flipper_id: nil
74
73
  end
75
74
 
76
75
  it 'returns correct error response' do
77
76
  expect(last_response.status).to eq(422)
78
- expect(json_response).to eq({ 'code' => 4, 'message' => 'Required parameter flipper_id is missing.', 'more_info' => 'https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference' })
77
+ expect(json_response).to eq(api_flipper_id_is_missing_response)
79
78
  end
80
79
  end
81
80
 
82
81
  describe 'disable nil flipper_id parameter' do
83
82
  before do
84
83
  flipper[:my_feature].enable
85
- delete '/api/v1/features/my_feature/actors', { flipper_id: nil }
84
+ delete '/features/my_feature/actors', flipper_id: nil
86
85
  end
87
86
 
88
87
  it 'returns correct error response' do
89
88
  expect(last_response.status).to eq(422)
90
- expect(json_response).to eq({ 'code' => 4, 'message' => 'Required parameter flipper_id is missing.', 'more_info' => 'https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference' })
89
+ expect(json_response).to eq(api_flipper_id_is_missing_response)
91
90
  end
92
91
  end
93
92
 
94
93
  describe 'enable missing feature' do
95
94
  before do
96
- post '/api/v1/features/my_feature/actors'
95
+ post '/features/my_feature/actors', flipper_id: actor.flipper_id
97
96
  end
98
97
 
99
- it 'returns correct error response' do
100
- expect(last_response.status).to eq(404)
101
- expect(json_response).to eq({ 'code' => 1, 'message' => 'Feature not found.', 'more_info' => 'https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference' })
98
+ it 'enables feature for actor' do
99
+ expect(last_response.status).to eq(200)
100
+ expect(flipper[:my_feature].enabled?(actor)).to be_truthy
101
+ expect(flipper[:my_feature].enabled_gate_names).to eq([:actor])
102
+ end
103
+
104
+ it 'returns decorated feature with actor enabled' do
105
+ gate = json_response['gates'].find { |gate| gate['key'] == 'actors' }
106
+ expect(gate['value']).to eq(['1'])
102
107
  end
103
108
  end
104
109
 
105
110
  describe 'disable missing feature' do
106
111
  before do
107
- delete '/api/v1/features/my_feature/actors'
112
+ delete '/features/my_feature/actors', flipper_id: actor.flipper_id
108
113
  end
109
114
 
110
- it 'returns correct error response' do
111
- expect(last_response.status).to eq(404)
112
- expect(json_response).to eq({ 'code' => 1, 'message' => 'Feature not found.', 'more_info' => 'https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference' })
115
+ it 'disables feature' do
116
+ expect(last_response.status).to eq(200)
117
+ expect(flipper[:my_feature].enabled?(actor)).to be_falsy
118
+ expect(flipper[:my_feature].enabled_gate_names).to be_empty
119
+ end
120
+
121
+ it 'returns decorated feature with boolean gate disabled' do
122
+ gate = json_response['gates'].find { |gate| gate['key'] == 'actors' }
123
+ expect(gate['value']).to be_empty
113
124
  end
114
125
  end
115
126
  end
@@ -6,7 +6,7 @@ RSpec.describe Flipper::Api::V1::Actions::BooleanGate do
6
6
  describe 'enable' do
7
7
  before do
8
8
  flipper[:my_feature].disable
9
- post '/api/v1/features/my_feature/boolean'
9
+ post '/features/my_feature/boolean'
10
10
  end
11
11
 
12
12
  it 'enables feature' do
@@ -23,7 +23,7 @@ RSpec.describe Flipper::Api::V1::Actions::BooleanGate do
23
23
  describe 'disable' do
24
24
  before do
25
25
  flipper[:my_feature].enable
26
- delete '/api/v1/features/my_feature/boolean'
26
+ delete '/features/my_feature/boolean'
27
27
  end
28
28
 
29
29
  it 'disables feature' do
@@ -0,0 +1,27 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe Flipper::Api::V1::Actions::ClearFeature do
4
+ let(:app) { build_api(flipper) }
5
+
6
+ describe 'clear' do
7
+ before do
8
+ Flipper.register(:admins) {}
9
+ actor_class = Struct.new(:flipper_id)
10
+ actor22 = actor_class.new('22')
11
+
12
+ feature = flipper[:my_feature]
13
+ feature.enable flipper.boolean
14
+ feature.enable flipper.group(:admins)
15
+ feature.enable flipper.actor(actor22)
16
+ feature.enable flipper.actors(25)
17
+ feature.enable flipper.time(45)
18
+
19
+ delete '/features/my_feature/clear'
20
+ end
21
+
22
+ it 'clears feature' do
23
+ expect(last_response.status).to eq(204)
24
+ expect(flipper[:my_feature].off?).to be_truthy
25
+ end
26
+ end
27
+ end
@@ -7,10 +7,9 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
7
7
 
8
8
  describe 'get' do
9
9
  context 'enabled feature' do
10
-
11
10
  before do
12
11
  flipper[:my_feature].enable
13
- get 'api/v1/features/my_feature'
12
+ get '/features/my_feature'
14
13
  end
15
14
 
16
15
  it 'responds with correct attributes' do
@@ -21,7 +20,7 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
21
20
  {
22
21
  'key' => 'boolean',
23
22
  'name' => 'boolean',
24
- 'value' => true,
23
+ 'value' => 'true',
25
24
  },
26
25
  {
27
26
  'key' => 'groups',
@@ -36,14 +35,14 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
36
35
  {
37
36
  'key' => 'percentage_of_actors',
38
37
  'name' => 'percentage_of_actors',
39
- 'value' => 0,
38
+ 'value' => nil,
40
39
  },
41
40
  {
42
41
  'key' => 'percentage_of_time',
43
42
  'name' => 'percentage_of_time',
44
- 'value' => 0,
45
- }
46
- ]
43
+ 'value' => nil,
44
+ },
45
+ ],
47
46
  }
48
47
 
49
48
  expect(last_response.status).to eq(200)
@@ -54,7 +53,7 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
54
53
  context 'disabled feature' do
55
54
  before do
56
55
  flipper[:my_feature].disable
57
- get 'api/v1/features/my_feature'
56
+ get '/features/my_feature'
58
57
  end
59
58
 
60
59
  it 'responds with correct attributes' do
@@ -65,12 +64,12 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
65
64
  {
66
65
  'key' => 'boolean',
67
66
  'name' => 'boolean',
68
- 'value' => false,
67
+ 'value' => nil,
69
68
  },
70
69
  {
71
- 'key'=> 'groups',
72
- 'name'=> 'group',
73
- 'value'=> [],
70
+ 'key' => 'groups',
71
+ 'name' => 'group',
72
+ 'value' => [],
74
73
  },
75
74
  {
76
75
  'key' => 'actors',
@@ -80,14 +79,14 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
80
79
  {
81
80
  'key' => 'percentage_of_actors',
82
81
  'name' => 'percentage_of_actors',
83
- 'value'=> 0,
82
+ 'value' => nil,
84
83
  },
85
84
  {
86
85
  'key' => 'percentage_of_time',
87
86
  'name' => 'percentage_of_time',
88
- 'value' => 0,
89
- }
90
- ]
87
+ 'value' => nil,
88
+ },
89
+ ],
91
90
  }
92
91
 
93
92
  expect(last_response.status).to eq(200)
@@ -97,15 +96,17 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
97
96
 
98
97
  context 'feature does not exist' do
99
98
  before do
100
- get 'api/v1/features/not_a_feature'
99
+ get '/features/not_a_feature'
101
100
  end
102
101
 
103
- it 'returns 404' do
102
+ it '404s' do
104
103
  expect(last_response.status).to eq(404)
105
- end
106
-
107
- it 'returns formatted error response body' do
108
- expect(json_response).to eq({ "code" => 1, "message" => "Feature not found.", "more_info" => "https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference" })
104
+ expected = {
105
+ 'code' => 1,
106
+ 'message' => 'Feature not found.',
107
+ 'more_info' => api_error_code_reference_url,
108
+ }
109
+ expect(json_response).to eq(expected)
109
110
  end
110
111
  end
111
112
  end
@@ -115,7 +116,7 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
115
116
  it 'deletes feature' do
116
117
  flipper[:my_feature].enable
117
118
  expect(flipper.features.map(&:key)).to include('my_feature')
118
- delete 'api/v1/features/my_feature'
119
+ delete '/features/my_feature'
119
120
  expect(last_response.status).to eq(204)
120
121
  expect(flipper.features.map(&:key)).not_to include('my_feature')
121
122
  end
@@ -123,15 +124,12 @@ RSpec.describe Flipper::Api::V1::Actions::Feature do
123
124
 
124
125
  context 'feature not found' do
125
126
  before do
126
- delete 'api/v1/features/my_feature'
127
+ delete '/features/my_feature'
127
128
  end
128
129
 
129
- it 'returns 404' do
130
- expect(last_response.status).to eq(404)
131
- end
132
-
133
- it 'returns formatted error response body' do
134
- expect(json_response).to eq({ "code" => 1, "message" => "Feature not found.", "more_info" => "https://github.com/jnunemaker/flipper/tree/master/docs/api#error-code-reference" })
130
+ it 'responds with 204' do
131
+ expect(last_response.status).to eq(204)
132
+ expect(flipper.features.map(&:key)).not_to include('my_feature')
135
133
  end
136
134
  end
137
135
  end