flipper 1.1.1 → 1.2.0

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 (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