flipper 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +25 -1
  3. data/.github/workflows/examples.yml +7 -1
  4. data/Changelog.md +1 -638
  5. data/Gemfile +5 -1
  6. data/README.md +21 -21
  7. data/Rakefile +2 -2
  8. data/exe/flipper +5 -0
  9. data/flipper.gemspec +6 -2
  10. data/lib/flipper/adapters/http/client.rb +25 -16
  11. data/lib/flipper/adapters/strict.rb +11 -8
  12. data/lib/flipper/cli.rb +240 -0
  13. data/lib/flipper/cloud/configuration.rb +7 -1
  14. data/lib/flipper/cloud/middleware.rb +5 -5
  15. data/lib/flipper/cloud/telemetry/submitter.rb +2 -2
  16. data/lib/flipper/cloud.rb +1 -1
  17. data/lib/flipper/engine.rb +32 -17
  18. data/lib/flipper/instrumentation/log_subscriber.rb +12 -3
  19. data/lib/flipper/metadata.rb +3 -1
  20. data/lib/flipper/test_help.rb +36 -0
  21. data/lib/flipper/version.rb +11 -1
  22. data/lib/generators/flipper/setup_generator.rb +63 -0
  23. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  24. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  25. data/lib/generators/flipper/update_generator.rb +35 -0
  26. data/spec/fixtures/environment.rb +1 -0
  27. data/spec/flipper/adapter_builder_spec.rb +1 -2
  28. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  29. data/spec/flipper/adapters/http_spec.rb +92 -75
  30. data/spec/flipper/adapters/strict_spec.rb +11 -9
  31. data/spec/flipper/cli_spec.rb +164 -0
  32. data/spec/flipper/cloud/configuration_spec.rb +9 -2
  33. data/spec/flipper/cloud/dsl_spec.rb +5 -5
  34. data/spec/flipper/cloud/middleware_spec.rb +8 -8
  35. data/spec/flipper/cloud/telemetry/submitter_spec.rb +24 -24
  36. data/spec/flipper/cloud/telemetry_spec.rb +1 -1
  37. data/spec/flipper/cloud_spec.rb +4 -4
  38. data/spec/flipper/engine_spec.rb +76 -11
  39. data/spec/flipper/instrumentation/log_subscriber_spec.rb +9 -2
  40. data/spec/flipper_spec.rb +1 -1
  41. data/spec/spec_helper.rb +1 -0
  42. data/spec/support/spec_helpers.rb +10 -4
  43. data/test_rails/generators/flipper/setup_generator_test.rb +64 -0
  44. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  45. data/test_rails/helper.rb +19 -2
  46. data/test_rails/system/test_help_test.rb +46 -0
  47. metadata +25 -8
@@ -0,0 +1,164 @@
1
+ require "flipper/cli"
2
+
3
+ RSpec.describe Flipper::CLI do
4
+ # Infer the command from the description
5
+ subject(:argv) do
6
+ descriptions = self.class.parent_groups.map {|g| g.metadata[:description_args] }.reverse.flatten.drop(1)
7
+ descriptions.map { |arg| Shellwords.split(arg) }.flatten
8
+ end
9
+
10
+ subject { run argv }
11
+
12
+ before do
13
+ ENV["FLIPPER_REQUIRE"] = "./spec/fixtures/environment"
14
+ end
15
+
16
+ describe "enable" do
17
+ describe "feature" do
18
+ it do
19
+ expect(subject).to have_attributes(status: 0, stdout: /feature.*enabled/)
20
+ expect(Flipper).to be_enabled(:feature)
21
+ end
22
+ end
23
+
24
+ describe "-a User;1 feature" do
25
+ it do
26
+ expect(subject).to have_attributes(status: 0, stdout: /feature.*enabled.*User;1/m)
27
+ expect(Flipper).to be_enabled(:feature, Flipper::Actor.new("User;1"))
28
+ end
29
+ end
30
+
31
+ describe "feature -g admins" do
32
+ it do
33
+ expect(subject).to have_attributes(status: 0, stdout: /feature.*enabled.*admins/m)
34
+ expect(Flipper.feature('feature').enabled_groups.map(&:name)).to eq([:admins])
35
+ end
36
+ end
37
+
38
+ describe "feature -p 30" do
39
+ it do
40
+ expect(subject).to have_attributes(status: 0, stdout: /feature.*enabled.*30% of actors/m)
41
+ expect(Flipper.feature('feature').percentage_of_actors_value).to eq(30)
42
+ end
43
+ end
44
+
45
+ describe "feature -t 50" do
46
+ it do
47
+ expect(subject).to have_attributes(status: 0, stdout: /feature.*enabled.*50% of time/m)
48
+ expect(Flipper.feature('feature').percentage_of_time_value).to eq(50)
49
+ end
50
+ end
51
+
52
+ describe %|feature -x '{"Equal":[{"Property":"flipper_id"},"User;1"]}'| do
53
+ it do
54
+ expect(subject).to have_attributes(status: 0, stdout: /feature.*enabled.*User;1/m)
55
+ expect(Flipper.feature('feature').expression.value).to eq({ "Equal" => [ { "Property" => ["flipper_id"] }, "User;1" ] })
56
+ end
57
+ end
58
+
59
+ describe %|feature -x invalid_json| do
60
+ it do
61
+ expect(subject).to have_attributes(status: 1, stderr: /JSON parse error/m)
62
+ end
63
+ end
64
+
65
+ describe %|feature -x '{}'| do
66
+ it do
67
+ expect(subject).to have_attributes(status: 1, stderr: /Invalid expression/m)
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "disable" do
73
+ describe "feature" do
74
+ before { Flipper.enable :feature }
75
+
76
+ it do
77
+ expect(subject).to have_attributes(status: 0, stdout: /feature.*disabled/)
78
+ expect(Flipper).not_to be_enabled(:feature)
79
+ end
80
+ end
81
+
82
+ describe "feature -g admins" do
83
+ before { Flipper.enable_group(:feature, :admins) }
84
+
85
+ it do
86
+ expect(subject).to have_attributes(status: 0, stdout: /feature.*disabled/)
87
+ expect(Flipper.feature('feature').enabled_groups).to be_empty
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "list" do
93
+ before do
94
+ Flipper.enable :foo
95
+ Flipper.disable :bar
96
+ end
97
+
98
+ it "lists features" do
99
+ expect(subject).to have_attributes(status: 0, stdout: /foo.*enabled/)
100
+ expect(subject).to have_attributes(status: 0, stdout: /bar.*disabled/)
101
+ end
102
+ end
103
+
104
+ ["-h", "--help", "help"].each do |arg|
105
+ describe arg do
106
+ it { should have_attributes(status: 0, stdout: /Usage: flipper/) }
107
+
108
+ it "should list subcommands" do
109
+ %w(enable disable list).each do |subcommand|
110
+ expect(subject.stdout).to match(/#{subcommand}/)
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "help enable" do
117
+ it { should have_attributes(status: 0, stdout: /Usage: flipper enable \[options\] <feature>/) }
118
+ end
119
+
120
+ describe "nope" do
121
+ it { should have_attributes(status: 1, stderr: /Unknown command: nope/) }
122
+ end
123
+
124
+ describe "--nope" do
125
+ it { should have_attributes(status: 1, stderr: /invalid option: --nope/) }
126
+ end
127
+
128
+ describe "show foo" do
129
+ context "boolean" do
130
+ before { Flipper.enable :foo }
131
+ it { should have_attributes(status: 0, stdout: /foo.*enabled/) }
132
+ end
133
+
134
+ context "actors" do
135
+ before { Flipper.enable_actor :foo, Flipper::Actor.new("User;1") }
136
+ it { should have_attributes(status: 0, stdout: /User;1/) }
137
+ end
138
+
139
+ context "groups" do
140
+ before { Flipper.enable_group :foo, :admins }
141
+ it { should have_attributes(status: 0, stdout: /enabled.*admins/m) }
142
+ end
143
+ end
144
+
145
+ def run(argv)
146
+ original_stdout = $stdout
147
+ original_stderr = $stderr
148
+
149
+ $stdout = StringIO.new
150
+ $stderr = StringIO.new
151
+ status = 0
152
+
153
+ begin
154
+ Flipper::CLI.run(argv)
155
+ rescue SystemExit => e
156
+ status = e.status
157
+ end
158
+
159
+ OpenStruct.new(status: status, stdout: $stdout.string, stderr: $stderr.string)
160
+ ensure
161
+ $stdout = original_stdout
162
+ $stderr = original_stderr
163
+ end
164
+ end
@@ -86,6 +86,13 @@ RSpec.describe Flipper::Cloud::Configuration do
86
86
  expect(instance.debug_output).to eq(STDOUT)
87
87
  end
88
88
 
89
+ it "defaults debug_output to STDOUT if FLIPPER_CLOUD_DEBUG_OUTPUT_STDOUT set to true" do
90
+ with_env "FLIPPER_CLOUD_DEBUG_OUTPUT_STDOUT" => "true" do
91
+ instance = described_class.new(required_options)
92
+ expect(instance.debug_output).to eq(STDOUT)
93
+ end
94
+ end
95
+
89
96
  it "defaults adapter block" do
90
97
  # The initial sync of http to local invokes this web request.
91
98
  stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
@@ -233,9 +240,9 @@ RSpec.describe Flipper::Cloud::Configuration do
233
240
  stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
234
241
  with({
235
242
  headers: {
236
- 'Flipper-Cloud-Token'=>'asdf',
243
+ 'flipper-cloud-token'=>'asdf',
237
244
  },
238
- }).to_return(status: 200, body: body, headers: {})
245
+ }).to_return(status: 200, body: body)
239
246
  instance = described_class.new(required_options)
240
247
  instance.sync
241
248
 
@@ -18,7 +18,7 @@ RSpec.describe Flipper::Cloud::DSL do
18
18
  stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
19
19
  with({
20
20
  headers: {
21
- 'Flipper-Cloud-Token'=>'asdf',
21
+ 'flipper-cloud-token'=>'asdf',
22
22
  },
23
23
  }).to_return(status: 200, body: '{"features": {}}', headers: {})
24
24
  cloud_configuration = Flipper::Cloud::Configuration.new({
@@ -65,11 +65,11 @@ RSpec.describe Flipper::Cloud::DSL do
65
65
 
66
66
  it "sends writes to cloud and local" do
67
67
  add_stub = stub_request(:post, "https://www.flippercloud.io/adapter/features").
68
- with({headers: {'Flipper-Cloud-Token'=>'asdf'}}).
69
- to_return(status: 200, body: '{}', headers: {})
68
+ with({headers: {'flipper-cloud-token'=>'asdf'}}).
69
+ to_return(status: 200, body: '{}')
70
70
  enable_stub = stub_request(:post, "https://www.flippercloud.io/adapter/features/foo/boolean").
71
- with(headers: {'Flipper-Cloud-Token'=>'asdf'}).
72
- to_return(status: 200, body: '{}', headers: {})
71
+ with(headers: {'flipper-cloud-token'=>'asdf'}).
72
+ to_return(status: 200, body: '{}')
73
73
 
74
74
  subject.enable(:foo)
75
75
 
@@ -101,8 +101,8 @@ RSpec.describe Flipper::Cloud::Middleware do
101
101
  post '/', request_body, env
102
102
 
103
103
  expect(last_response.status).to eq(402)
104
- expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
105
- expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to include("Failed with status: 402")
104
+ expect(last_response.headers["flipper-cloud-response-error-class"]).to eq("Flipper::Adapters::Http::Error")
105
+ expect(last_response.headers["flipper-cloud-response-error-message"]).to include("Failed with status: 402")
106
106
  expect(stub).to have_been_requested
107
107
  end
108
108
  end
@@ -124,8 +124,8 @@ RSpec.describe Flipper::Cloud::Middleware do
124
124
  post '/', request_body, env
125
125
 
126
126
  expect(last_response.status).to eq(500)
127
- expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
128
- expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to include("Failed with status: 503")
127
+ expect(last_response.headers["flipper-cloud-response-error-class"]).to eq("Flipper::Adapters::Http::Error")
128
+ expect(last_response.headers["flipper-cloud-response-error-message"]).to include("Failed with status: 503")
129
129
  expect(stub).to have_been_requested
130
130
  end
131
131
  end
@@ -147,8 +147,8 @@ RSpec.describe Flipper::Cloud::Middleware do
147
147
  post '/', request_body, env
148
148
 
149
149
  expect(last_response.status).to eq(500)
150
- expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Net::OpenTimeout")
151
- expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("execution expired")
150
+ expect(last_response.headers["flipper-cloud-response-error-class"]).to eq("Net::OpenTimeout")
151
+ expect(last_response.headers["flipper-cloud-response-error-message"]).to eq("execution expired")
152
152
  expect(stub).to have_been_requested
153
153
  end
154
154
  end
@@ -277,13 +277,13 @@ RSpec.describe Flipper::Cloud::Middleware do
277
277
  stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
278
278
  with({
279
279
  headers: {
280
- 'Flipper-Cloud-Token' => token,
280
+ 'flipper-cloud-token' => token,
281
281
  },
282
282
  })
283
283
  if status == :timeout
284
284
  stub.to_timeout
285
285
  else
286
- stub.to_return(status: status, body: response_body, headers: {})
286
+ stub.to_return(status: status, body: response_body)
287
287
  end
288
288
  end
289
289
  end
@@ -43,33 +43,33 @@ RSpec.describe Flipper::Cloud::Telemetry::Submitter do
43
43
  ]
44
44
  }
45
45
  expected_headers = {
46
- 'Accept' => 'application/json',
47
- 'Client-Engine' => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
48
- 'Client-Hostname' => Socket.gethostname,
49
- 'Client-Language' => 'ruby',
50
- 'Client-Language-Version' => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
51
- 'Client-Pid' => Process.pid.to_s,
52
- 'Client-Platform' => RUBY_PLATFORM,
53
- 'Client-Thread' => Thread.current.object_id.to_s,
54
- 'Content-Encoding' => 'gzip',
55
- 'Content-Type' => 'application/json',
56
- 'Flipper-Cloud-Token' => 'asdf',
57
- 'Schema-Version' => 'V1',
58
- 'User-Agent' => "Flipper HTTP Adapter v#{Flipper::VERSION}",
46
+ 'accept' => 'application/json',
47
+ 'client-engine' => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
48
+ 'client-hostname' => Socket.gethostname,
49
+ 'client-language' => 'ruby',
50
+ 'client-language-version' => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
51
+ 'client-pid' => Process.pid.to_s,
52
+ 'client-platform' => RUBY_PLATFORM,
53
+ 'client-thread' => Thread.current.object_id.to_s,
54
+ 'content-encoding' => 'gzip',
55
+ 'content-type' => 'application/json',
56
+ 'flipper-cloud-token' => 'asdf',
57
+ 'schema-version' => 'V1',
58
+ 'user-agent' => "Flipper HTTP Adapter v#{Flipper::VERSION}",
59
59
  }
60
60
  stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
61
61
  with(headers: expected_headers) { |request|
62
62
  gunzipped = Flipper::Typecast.from_gzip(request.body)
63
63
  body = Flipper::Typecast.from_json(gunzipped)
64
64
  body == expected_body
65
- }.to_return(status: 200, body: "{}", headers: {})
65
+ }.to_return(status: 200, body: "{}")
66
66
  subject.call(enabled_metrics)
67
67
  end
68
68
 
69
69
  it "defaults backoff_policy" do
70
70
  stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
71
- to_return(status: 429, body: "{}", headers: {}).
72
- to_return(status: 200, body: "{}", headers: {})
71
+ to_return(status: 429, body: "{}").
72
+ to_return(status: 200, body: "{}")
73
73
  instance = described_class.new(cloud_configuration)
74
74
  expect(instance.backoff_policy.min_timeout_ms).to eq(1_000)
75
75
  expect(instance.backoff_policy.max_timeout_ms).to eq(30_000)
@@ -77,7 +77,7 @@ RSpec.describe Flipper::Cloud::Telemetry::Submitter do
77
77
 
78
78
  it "tries 10 times by default" do
79
79
  stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
80
- to_return(status: 500, body: "{}", headers: {})
80
+ to_return(status: 500, body: "{}")
81
81
  subject.call(enabled_metrics)
82
82
  expect(subject.backoff_policy.retries).to eq(9) # 9 retries + 1 initial attempt
83
83
  end
@@ -111,19 +111,19 @@ RSpec.describe Flipper::Cloud::Telemetry::Submitter do
111
111
 
112
112
  it "retries on 429" do
113
113
  stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
114
- to_return(status: 429, body: "{}", headers: {}).
115
- to_return(status: 429, body: "{}", headers: {}).
116
- to_return(status: 200, body: "{}", headers: {})
114
+ to_return(status: 429, body: "{}").
115
+ to_return(status: 429, body: "{}").
116
+ to_return(status: 200, body: "{}")
117
117
  subject.call(enabled_metrics)
118
118
  expect(subject.backoff_policy.retries).to eq(2)
119
119
  end
120
120
 
121
121
  it "retries on 500" do
122
122
  stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
123
- to_return(status: 500, body: "{}", headers: {}).
124
- to_return(status: 503, body: "{}", headers: {}).
125
- to_return(status: 502, body: "{}", headers: {}).
126
- to_return(status: 200, body: "{}", headers: {})
123
+ to_return(status: 500, body: "{}").
124
+ to_return(status: 503, body: "{}").
125
+ to_return(status: 502, body: "{}").
126
+ to_return(status: 200, body: "{}")
127
127
  subject.call(enabled_metrics)
128
128
  expect(subject.backoff_policy.retries).to eq(3)
129
129
  end
@@ -4,7 +4,7 @@ require 'flipper/cloud/configuration'
4
4
  RSpec.describe Flipper::Cloud::Telemetry do
5
5
  it "phones home and does not update telemetry interval if missing" do
6
6
  stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
7
- to_return(status: 200, body: "{}", headers: {})
7
+ to_return(status: 200, body: "{}")
8
8
 
9
9
  cloud_configuration = Flipper::Cloud::Configuration.new(token: "test")
10
10
 
@@ -35,7 +35,7 @@ RSpec.describe Flipper::Cloud do
35
35
  expect(client.uri.scheme).to eq('https')
36
36
  expect(client.uri.host).to eq('www.flippercloud.io')
37
37
  expect(client.uri.path).to eq('/adapter')
38
- expect(client.headers['Flipper-Cloud-Token']).to eq(token)
38
+ expect(client.headers["flipper-cloud-token"]).to eq(token)
39
39
  expect(@instance.instrumenter).to be(Flipper::Instrumenters::Noop)
40
40
  end
41
41
  end
@@ -104,7 +104,7 @@ RSpec.describe Flipper::Cloud do
104
104
 
105
105
  it 'can import' do
106
106
  stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
107
- with(headers: {'Flipper-Cloud-Token'=>'asdf'}).to_return(status: 200, body: "{}", headers: {})
107
+ with(headers: {'flipper-cloud-token'=>'asdf'}).to_return(status: 200, body: "{}", headers: {})
108
108
 
109
109
  flipper = Flipper.new(Flipper::Adapters::Memory.new)
110
110
 
@@ -130,7 +130,7 @@ RSpec.describe Flipper::Cloud do
130
130
 
131
131
  it 'raises error for failure while importing' do
132
132
  stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
133
- with(headers: {'Flipper-Cloud-Token'=>'asdf'}).to_return(status: 500, body: "{}")
133
+ with(headers: {'flipper-cloud-token'=>'asdf'}).to_return(status: 500, body: "{}")
134
134
 
135
135
  flipper = Flipper.new(Flipper::Adapters::Memory.new)
136
136
 
@@ -155,7 +155,7 @@ RSpec.describe Flipper::Cloud do
155
155
 
156
156
  it 'raises error for timeout while importing' do
157
157
  stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
158
- with(headers: {'Flipper-Cloud-Token'=>'asdf'}).to_timeout
158
+ with(headers: {'flipper-cloud-token'=>'asdf'}).to_timeout
159
159
 
160
160
  flipper = Flipper.new(Flipper::Adapters::Memory.new)
161
161
 
@@ -45,7 +45,7 @@ RSpec.describe Flipper::Engine do
45
45
  subject
46
46
  expect(config.strict).to eq(:warn)
47
47
  expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
48
- expect(adapter.handler).to be(Flipper::Adapters::Strict::HANDLERS.fetch(:warn))
48
+ expect(adapter.handler).to be(:warn)
49
49
  end
50
50
  end
51
51
 
@@ -57,23 +57,47 @@ RSpec.describe Flipper::Engine do
57
57
  end
58
58
  end
59
59
 
60
- it "defaults to strict=false in RAILS_ENV=production" do
61
- Rails.env = "production"
60
+ [true, :raise, :warn].each do |value|
61
+ it "can set strict=#{value.inspect} in initializer" do
62
+ initializer { config.strict = value }
62
63
  subject
63
- expect(config.strict).to eq(false)
64
- expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
64
+ expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
65
+ expect(adapter.handler).to be(value)
66
+ end
67
+ end
68
+
69
+ it "can set strict=false in initializer" do
70
+ initializer { config.strict = false }
71
+ subject
72
+ expect(config.strict).to eq(false)
73
+ expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
74
+ end
75
+
76
+ it "defaults to strict=:warn in RAILS_ENV=development" do
77
+ Rails.env = "development"
78
+ subject
79
+ expect(config.strict).to eq(:warn)
80
+ expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
65
81
  end
66
82
 
67
- %w(development test).each do |env|
83
+ %w(production test).each do |env|
68
84
  it "defaults to strict=warn in RAILS_ENV=#{env}" do
69
85
  Rails.env = env
70
86
  expect(Rails.env).to eq(env)
71
87
  subject
72
- expect(config.strict).to eq(:warn)
73
- expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
74
- expect(adapter.handler).to be(Flipper::Adapters::Strict::HANDLERS.fetch(:warn))
88
+ expect(config.strict).to eq(false)
89
+ expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
75
90
  end
76
91
  end
92
+
93
+ it "defaults to strict=warn in RAILS_ENV=development" do
94
+ Rails.env = "development"
95
+ expect(Rails.env).to eq("development")
96
+ subject
97
+ expect(config.strict).to eq(:warn)
98
+ expect(adapter).to be_instance_of(Flipper::Adapters::Strict)
99
+ expect(adapter.handler).to be(:warn)
100
+ end
77
101
  end
78
102
 
79
103
  context 'cloudless' do
@@ -153,6 +177,46 @@ RSpec.describe Flipper::Engine do
153
177
  if: nil
154
178
  })
155
179
  end
180
+
181
+ context "test_help" do
182
+ it "is loaded if RAILS_ENV=test" do
183
+ Rails.env = "test"
184
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
185
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
186
+ subject
187
+ expect(config.test_help).to eq(true)
188
+ end
189
+
190
+ it "is loaded if FLIPPER_TEST_HELP=true" do
191
+ ENV["FLIPPER_TEST_HELP"] = "true"
192
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
193
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
194
+ subject
195
+ expect(config.test_help).to eq(true)
196
+ end
197
+
198
+ it "is loaded if config.flipper.test_help = true" do
199
+ initializer { config.test_help = true }
200
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
201
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help")
202
+ subject
203
+ end
204
+
205
+ it "is not loaded if FLIPPER_TEST_HELP=false" do
206
+ ENV["FLIPPER_TEST_HELP"] = "false"
207
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
208
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help").never
209
+ subject
210
+ end
211
+
212
+ it "is not loaded if config.flipper.test_help = false" do
213
+ Rails.env = "true"
214
+ initializer { config.test_help = false }
215
+ allow(Flipper::Engine.instance).to receive(:require).and_call_original
216
+ expect(Flipper::Engine.instance).to receive(:require).with("flipper/test_help").never
217
+ subject
218
+ end
219
+ end
156
220
  end
157
221
 
158
222
  context 'with cloud' do
@@ -167,7 +231,8 @@ RSpec.describe Flipper::Engine do
167
231
 
168
232
  it_behaves_like 'config.strict' do
169
233
  let(:adapter) do
170
- dual_write = Flipper.adapter.adapter
234
+ memoizable = Flipper.adapter
235
+ dual_write = memoizable.adapter
171
236
  poll = dual_write.local
172
237
  poll.adapter
173
238
  end
@@ -210,7 +275,7 @@ RSpec.describe Flipper::Engine do
210
275
  application.initialize!
211
276
 
212
277
  stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").with({
213
- headers: { "Flipper-Cloud-Token" => ENV["FLIPPER_CLOUD_TOKEN"] },
278
+ headers: { "flipper-cloud-token" => ENV["FLIPPER_CLOUD_TOKEN"] },
214
279
  }).to_return(status: 200, body: JSON.generate({ features: {} }), headers: {})
215
280
 
216
281
  post "/_flipper", request_body, { "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value }
@@ -1,6 +1,6 @@
1
1
  require 'logger'
2
- require 'flipper/adapters/instrumented'
3
2
  require 'flipper/instrumentation/log_subscriber'
3
+ require 'flipper/adapters/instrumented'
4
4
 
5
5
  begin
6
6
  require 'active_support/isolated_execution_state'
@@ -8,6 +8,9 @@ rescue LoadError
8
8
  # ActiveSupport::IsolatedExecutionState is only available in Rails 5.2+
9
9
  end
10
10
 
11
+ # Don't log in other tests, we'll manually re-attach when this one starts
12
+ Flipper::Instrumentation::LogSubscriber.detach
13
+
11
14
  RSpec.describe Flipper::Instrumentation::LogSubscriber do
12
15
  let(:adapter) do
13
16
  memory = Flipper::Adapters::Memory.new
@@ -32,8 +35,12 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
32
35
  described_class.logger = nil
33
36
  end
34
37
 
38
+ before(:all) do
39
+ described_class.attach
40
+ end
41
+
35
42
  after(:all) do
36
- ActiveSupport::Notifications.unsubscribe("flipper")
43
+ described_class.detach
37
44
  end
38
45
 
39
46
  let(:log) { @io.string }
data/spec/flipper_spec.rb CHANGED
@@ -241,7 +241,7 @@ RSpec.describe Flipper do
241
241
  stub = stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
242
242
  with({
243
243
  headers: {
244
- 'Flipper-Cloud-Token'=>'asdf',
244
+ 'flipper-cloud-token'=>'asdf',
245
245
  },
246
246
  }).to_return(status: 200, body: '{"features": {}}', headers: {})
247
247
  cloud_configuration = Flipper::Cloud::Configuration.new({
data/spec/spec_helper.rb CHANGED
@@ -18,6 +18,7 @@ require 'flipper'
18
18
  require 'flipper/api'
19
19
  require 'flipper/spec/shared_adapter_specs'
20
20
  require 'flipper/ui'
21
+ require 'flipper/test_help'
21
22
 
22
23
  Dir[FlipperRoot.join('spec/support/**/*.rb')].sort.each { |f| require f }
23
24
 
@@ -3,6 +3,8 @@ require 'json'
3
3
  require 'rack/test'
4
4
 
5
5
  module SpecHelpers
6
+ extend self
7
+
6
8
  def self.included(base)
7
9
  base.let(:flipper) { build_flipper }
8
10
  base.let(:app) { build_app(flipper) }
@@ -27,7 +29,11 @@ module SpecHelpers
27
29
  end
28
30
 
29
31
  def json_response
30
- JSON.parse(last_response.body)
32
+ body = last_response.body
33
+ if last_response["content-encoding"] == 'gzip'
34
+ body = Flipper::Typecast.from_gzip(body)
35
+ end
36
+ JSON.parse(body)
31
37
  end
32
38
 
33
39
  def api_error_code_reference_url
@@ -76,11 +82,11 @@ module SpecHelpers
76
82
 
77
83
  yield
78
84
 
79
- $stderr = original_stderr
80
- $stdout = original_stdout
81
-
82
85
  # Return output
83
86
  output.string
87
+ ensure
88
+ $stderr = original_stderr
89
+ $stdout = original_stdout
84
90
  end
85
91
  end
86
92