flipper 1.2.2 → 1.3.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -1
  3. data/.github/workflows/examples.yml +1 -1
  4. data/README.md +2 -0
  5. data/docs/images/banner.jpg +0 -0
  6. data/lib/flipper/adapters/actor_limit.rb +28 -0
  7. data/lib/flipper/adapters/cache_base.rb +143 -0
  8. data/lib/flipper/adapters/operation_logger.rb +18 -88
  9. data/lib/flipper/adapters/read_only.rb +6 -39
  10. data/lib/flipper/adapters/strict.rb +5 -10
  11. data/lib/flipper/adapters/wrapper.rb +54 -0
  12. data/lib/flipper/cli.rb +36 -17
  13. data/lib/flipper/cloud/configuration.rb +2 -3
  14. data/lib/flipper/cloud/telemetry/instrumenter.rb +4 -8
  15. data/lib/flipper/cloud/telemetry.rb +10 -2
  16. data/lib/flipper/engine.rb +5 -5
  17. data/lib/flipper/poller.rb +6 -5
  18. data/lib/flipper/serializers/gzip.rb +3 -5
  19. data/lib/flipper/serializers/json.rb +3 -5
  20. data/lib/flipper/spec/shared_adapter_specs.rb +17 -16
  21. data/lib/flipper/test/shared_adapter_test.rb +17 -17
  22. data/lib/flipper/typecast.rb +3 -3
  23. data/lib/flipper/version.rb +1 -1
  24. data/lib/flipper.rb +3 -1
  25. data/package-lock.json +41 -0
  26. data/package.json +10 -0
  27. data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
  28. data/spec/flipper/adapters/http_spec.rb +11 -2
  29. data/spec/flipper/cli_spec.rb +21 -46
  30. data/spec/flipper/cloud/configuration_spec.rb +2 -1
  31. data/spec/flipper/cloud/telemetry_spec.rb +52 -0
  32. data/spec/flipper/cloud_spec.rb +4 -2
  33. data/spec/flipper/engine_spec.rb +34 -4
  34. data/spec/flipper/middleware/memoizer_spec.rb +7 -4
  35. data/spec/support/fail_on_output.rb +8 -0
  36. data/spec/support/spec_helpers.rb +2 -1
  37. data/test/adapters/actor_limit_test.rb +20 -0
  38. data/test_rails/system/test_help_test.rb +1 -1
  39. metadata +14 -3
@@ -3,9 +3,11 @@ require "delegate"
3
3
  module Flipper
4
4
  module Cloud
5
5
  class Telemetry
6
- class Instrumenter < SimpleDelegator
6
+ class Instrumenter
7
+ attr_reader :instrumenter
8
+
7
9
  def initialize(cloud_configuration, instrumenter)
8
- super instrumenter
10
+ @instrumenter = instrumenter
9
11
  @cloud_configuration = cloud_configuration
10
12
  end
11
13
 
@@ -14,12 +16,6 @@ module Flipper
14
16
  @cloud_configuration.telemetry.record(name, payload)
15
17
  return_value
16
18
  end
17
-
18
- private
19
-
20
- def instrumenter
21
- __getobj__
22
- end
23
19
  end
24
20
  end
25
21
  end
@@ -160,8 +160,16 @@ module Flipper
160
160
  # thus may have a telemetry-interval header for us to respect.
161
161
  response ||= error.response if error && error.respond_to?(:response)
162
162
 
163
- if response && interval = response["telemetry-interval"]
164
- self.interval = interval.to_f
163
+ if response
164
+ if Flipper::Typecast.to_boolean(response["telemetry-shutdown"])
165
+ debug "action=telemetry_shutdown message=The server has requested that telemetry be shut down."
166
+ stop
167
+ return
168
+ end
169
+
170
+ if interval = response["telemetry-interval"]
171
+ self.interval = interval.to_f
172
+ end
165
173
  end
166
174
  rescue => error
167
175
  error "action=post_to_cloud error=#{error.inspect}"
@@ -25,6 +25,7 @@ module Flipper
25
25
  log: ENV.fetch('FLIPPER_LOG', 'true').casecmp('true').zero?,
26
26
  cloud_path: "_flipper",
27
27
  strict: default_strict_value,
28
+ actor_limit: ENV["FLIPPER_ACTOR_LIMIT"]&.to_i || 100,
28
29
  test_help: Flipper::Typecast.to_boolean(ENV["FLIPPER_TEST_HELP"] || Rails.env.test?),
29
30
  )
30
31
  end
@@ -65,13 +66,12 @@ module Flipper
65
66
  end
66
67
  end
67
68
 
68
- initializer "flipper.strict", after: :load_config_initializers do |app|
69
+ initializer "flipper.adapters", after: :load_config_initializers do |app|
69
70
  flipper = app.config.flipper
70
71
 
71
- if flipper.strict
72
- Flipper.configure do |config|
73
- config.use Flipper::Adapters::Strict, flipper.strict
74
- end
72
+ Flipper.configure do |config|
73
+ config.use Flipper::Adapters::Strict, flipper.strict if flipper.strict
74
+ config.use Flipper::Adapters::ActorLimit, flipper.actor_limit if flipper.actor_limit
75
75
  end
76
76
  end
77
77
 
@@ -20,6 +20,8 @@ module Flipper
20
20
  instances.each {|_, instance| instance.stop }.clear
21
21
  end
22
22
 
23
+ MINIMUM_POLL_INTERVAL = 10
24
+
23
25
  def initialize(options = {})
24
26
  @thread = nil
25
27
  @pid = Process.pid
@@ -30,9 +32,9 @@ module Flipper
30
32
  @last_synced_at = Concurrent::AtomicFixnum.new(0)
31
33
  @adapter = Adapters::Memory.new(nil, threadsafe: true)
32
34
 
33
- if @interval < 1
34
- warn "Flipper::Cloud poll interval must be greater than or equal to 1 but was #{@interval}. Setting @interval to 1."
35
- @interval = 1
35
+ if @interval < MINIMUM_POLL_INTERVAL
36
+ warn "Flipper::Cloud poll interval must be greater than or equal to #{MINIMUM_POLL_INTERVAL} but was #{@interval}. Setting @interval to #{MINIMUM_POLL_INTERVAL}."
37
+ @interval = MINIMUM_POLL_INTERVAL
36
38
  end
37
39
 
38
40
  @start_automatically = options.fetch(:start_automatically, true)
@@ -64,8 +66,7 @@ module Flipper
64
66
  # you can instrument these using poller.flipper
65
67
  end
66
68
 
67
- sleep_interval = interval - (Concurrent.monotonic_time - start)
68
- sleep sleep_interval if sleep_interval.positive?
69
+ sleep interval
69
70
  end
70
71
  end
71
72
 
@@ -3,10 +3,8 @@ require "stringio"
3
3
 
4
4
  module Flipper
5
5
  module Serializers
6
- module Gzip
7
- module_function
8
-
9
- def serialize(source)
6
+ class Gzip
7
+ def self.serialize(source)
10
8
  return if source.nil?
11
9
  output = StringIO.new
12
10
  gz = Zlib::GzipWriter.new(output)
@@ -15,7 +13,7 @@ module Flipper
15
13
  output.string
16
14
  end
17
15
 
18
- def deserialize(source)
16
+ def self.deserialize(source)
19
17
  return if source.nil?
20
18
  Zlib::GzipReader.wrap(StringIO.new(source), &:read)
21
19
  end
@@ -2,15 +2,13 @@ require "json"
2
2
 
3
3
  module Flipper
4
4
  module Serializers
5
- module Json
6
- module_function
7
-
8
- def serialize(source)
5
+ class Json
6
+ def self.serialize(source)
9
7
  return if source.nil?
10
8
  JSON.generate(source)
11
9
  end
12
10
 
13
- def deserialize(source)
11
+ def self.deserialize(source)
14
12
  return if source.nil?
15
13
  JSON.parse(source)
16
14
  end
@@ -108,19 +108,19 @@ RSpec.shared_examples_for 'a flipper adapter' do
108
108
  actor22 = Flipper::Actor.new('22')
109
109
  actor_asdf = Flipper::Actor.new('asdf')
110
110
 
111
- expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
112
- expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor_asdf))).to eq(true)
111
+ expect(feature.enable(actor22)).to be(true)
112
+ expect(feature.enable(actor_asdf)).to be(true)
113
113
 
114
- result = subject.get(feature)
115
- expect(result[:actors]).to eq(Set['22', 'asdf'])
114
+ expect(feature).to be_enabled(actor22)
115
+ expect(feature).to be_enabled(actor_asdf)
116
116
 
117
- expect(subject.disable(feature, actor_gate, Flipper::Types::Actor.new(actor22))).to eq(true)
118
- result = subject.get(feature)
119
- expect(result[:actors]).to eq(Set['asdf'])
117
+ expect(feature.disable(actor22)).to be(true)
118
+ expect(feature).not_to be_enabled(actor22)
119
+ expect(feature).to be_enabled(actor_asdf)
120
120
 
121
- expect(subject.disable(feature, actor_gate, Flipper::Types::Actor.new(actor_asdf))).to eq(true)
122
- result = subject.get(feature)
123
- expect(result[:actors]).to eq(Set.new)
121
+ expect(feature.disable(actor_asdf)).to eq(true)
122
+ expect(feature).not_to be_enabled(actor22)
123
+ expect(feature).not_to be_enabled(actor_asdf)
124
124
  end
125
125
 
126
126
  it 'can enable, disable and get value for percentage of actors gate' do
@@ -182,9 +182,10 @@ RSpec.shared_examples_for 'a flipper adapter' do
182
182
  end
183
183
 
184
184
  it 'converts the actor value to a string' do
185
- expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(Flipper::Actor.new(22)))).to eq(true)
186
- result = subject.get(feature)
187
- expect(result[:actors]).to eq(Set['22'])
185
+ actor = Flipper::Actor.new(22)
186
+ expect(feature).not_to be_enabled(actor)
187
+ feature.enable_actor actor
188
+ expect(feature).to be_enabled(actor)
188
189
  end
189
190
 
190
191
  it 'converts group value to a string' do
@@ -295,9 +296,9 @@ RSpec.shared_examples_for 'a flipper adapter' do
295
296
 
296
297
  it 'can double enable an actor without error' do
297
298
  actor = Flipper::Actor.new('Flipper::Actor;22')
298
- expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor))).to eq(true)
299
- expect(subject.enable(feature, actor_gate, Flipper::Types::Actor.new(actor))).to eq(true)
300
- expect(subject.get(feature).fetch(:actors)).to eq(Set['Flipper::Actor;22'])
299
+ expect(feature.enable(actor)).to be(true)
300
+ expect(feature.enable(actor)).to be(true)
301
+ expect(feature).to be_enabled(actor)
301
302
  end
302
303
 
303
304
  it 'can double enable a group without error' do
@@ -104,19 +104,19 @@ module Flipper
104
104
  actor22 = Flipper::Actor.new('22')
105
105
  actor_asdf = Flipper::Actor.new('asdf')
106
106
 
107
- assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor22))
108
- assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor_asdf))
107
+ assert_equal true, @feature.enable(actor22)
108
+ assert_equal true, @feature.enable(actor_asdf)
109
109
 
110
- result = @adapter.get(@feature)
111
- assert_equal Set['22', 'asdf'], result[:actors]
110
+ assert @feature.enabled?(actor22)
111
+ assert @feature.enabled?(actor_asdf)
112
112
 
113
- assert true, @adapter.disable(@feature, @actor_gate, Flipper::Types::Actor.new(actor22))
114
- result = @adapter.get(@feature)
115
- assert_equal Set['asdf'], result[:actors]
113
+ assert_equal true, @feature.disable(actor22)
114
+ refute @feature.enabled?(actor22)
115
+ assert @feature.enabled?(actor_asdf)
116
116
 
117
- assert_equal true, @adapter.disable(@feature, @actor_gate, Flipper::Types::Actor.new(actor_asdf))
118
- result = @adapter.get(@feature)
119
- assert_equal Set.new, result[:actors]
117
+ assert_equal true, @feature.disable(actor_asdf)
118
+ refute @feature.enabled?(actor22)
119
+ refute @feature.enabled?(actor_asdf)
120
120
  end
121
121
 
122
122
  def test_can_enable_disable_get_value_for_percentage_of_actors_gate
@@ -178,10 +178,10 @@ module Flipper
178
178
  end
179
179
 
180
180
  def test_converts_the_actor_value_to_a_string
181
- assert_equal true,
182
- @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(Flipper::Actor.new(22)))
183
- result = @adapter.get(@feature)
184
- assert_equal Set['22'], result[:actors]
181
+ actor = Flipper::Actor.new(22)
182
+ refute @feature.enabled?(actor)
183
+ @feature.enable_actor actor
184
+ assert @feature.enabled?(actor)
185
185
  end
186
186
 
187
187
  def test_converts_group_value_to_a_string
@@ -292,9 +292,9 @@ module Flipper
292
292
 
293
293
  def test_can_double_enable_an_actor_without_error
294
294
  actor = Flipper::Actor.new('Flipper::Actor;22')
295
- assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor))
296
- assert_equal true, @adapter.enable(@feature, @actor_gate, Flipper::Types::Actor.new(actor))
297
- assert_equal Set['Flipper::Actor;22'], @adapter.get(@feature).fetch(:actors)
295
+ assert_equal true, @feature.enable(actor)
296
+ assert_equal true, @feature.enable(actor)
297
+ assert @feature.enabled?(actor)
298
298
  end
299
299
 
300
300
  def test_can_double_enable_a_group_without_error
@@ -3,8 +3,8 @@ require "flipper/serializers/json"
3
3
  require "flipper/serializers/gzip"
4
4
 
5
5
  module Flipper
6
- module Typecast
7
- TruthMap = {
6
+ class Typecast
7
+ TRUTH_MAP = {
8
8
  true => true,
9
9
  1 => true,
10
10
  'true' => true,
@@ -15,7 +15,7 @@ module Flipper
15
15
  #
16
16
  # Returns true or false.
17
17
  def self.to_boolean(value)
18
- !!TruthMap[value]
18
+ !!TRUTH_MAP[value]
19
19
  end
20
20
 
21
21
  # Internal: Convert value to an integer.
@@ -1,5 +1,5 @@
1
1
  module Flipper
2
- VERSION = '1.2.2'.freeze
2
+ VERSION = '1.3.0'.freeze
3
3
 
4
4
  REQUIRED_RUBY_VERSION = '2.6'.freeze
5
5
  NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
data/lib/flipper.rb CHANGED
@@ -174,10 +174,12 @@ end
174
174
 
175
175
  require 'flipper/actor'
176
176
  require 'flipper/adapter'
177
+ require 'flipper/adapters/wrapper'
178
+ require 'flipper/adapters/actor_limit'
179
+ require 'flipper/adapters/instrumented'
177
180
  require 'flipper/adapters/memoizable'
178
181
  require 'flipper/adapters/memory'
179
182
  require 'flipper/adapters/strict'
180
- require 'flipper/adapters/instrumented'
181
183
  require 'flipper/adapter_builder'
182
184
  require 'flipper/configuration'
183
185
  require 'flipper/dsl'
data/package-lock.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "flipper",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "hasInstallScript": true,
8
+ "dependencies": {
9
+ "@popperjs/core": "^2.11.8",
10
+ "bootstrap": "^5.3.3"
11
+ }
12
+ },
13
+ "node_modules/@popperjs/core": {
14
+ "version": "2.11.8",
15
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
16
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
17
+ "funding": {
18
+ "type": "opencollective",
19
+ "url": "https://opencollective.com/popperjs"
20
+ }
21
+ },
22
+ "node_modules/bootstrap": {
23
+ "version": "5.3.3",
24
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
25
+ "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
26
+ "funding": [
27
+ {
28
+ "type": "github",
29
+ "url": "https://github.com/sponsors/twbs"
30
+ },
31
+ {
32
+ "type": "opencollective",
33
+ "url": "https://opencollective.com/bootstrap"
34
+ }
35
+ ],
36
+ "peerDependencies": {
37
+ "@popperjs/core": "^2.11.8"
38
+ }
39
+ }
40
+ }
41
+ }
data/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "private": true,
3
+ "dependencies": {
4
+ "@popperjs/core": "^2.11.8",
5
+ "bootstrap": "^5.3.3"
6
+ },
7
+ "scripts": {
8
+ "postinstall": "script/vendor-assets"
9
+ }
10
+ }
@@ -0,0 +1,20 @@
1
+ require "flipper/adapters/actor_limit"
2
+
3
+ RSpec.describe Flipper::Adapters::ActorLimit do
4
+ it_should_behave_like 'a flipper adapter' do
5
+ let(:limit) { 5 }
6
+ let(:adapter) { Flipper::Adapters::ActorLimit.new(Flipper::Adapters::Memory.new, limit) }
7
+
8
+ subject { adapter }
9
+
10
+ describe '#enable' do
11
+ it "fails when limit exceeded" do
12
+ 5.times { |i| feature.enable Flipper::Actor.new("User;#{i}") }
13
+
14
+ expect {
15
+ feature.enable Flipper::Actor.new("User;6")
16
+ }.to raise_error(Flipper::Adapters::ActorLimit::LimitExceeded)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,6 +1,15 @@
1
1
  require 'flipper/adapters/http'
2
2
  require 'flipper/adapters/pstore'
3
- require 'rack/handler/webrick'
3
+
4
+ rack_handler = begin
5
+ # Rack 3+
6
+ require 'rackup/handler/webrick'
7
+ Rackup::Handler::WEBrick
8
+ rescue LoadError
9
+ require 'rack/handler/webrick'
10
+ Rack::Handler::WEBrick
11
+ end
12
+
4
13
 
5
14
  FLIPPER_SPEC_API_PORT = ENV.fetch('FLIPPER_SPEC_API_PORT', 9001).to_i
6
15
 
@@ -36,7 +45,7 @@ RSpec.describe Flipper::Adapters::Http do
36
45
  ],
37
46
  }
38
47
  @server = WEBrick::HTTPServer.new(server_options)
39
- @server.mount '/', Rack::Handler::WEBrick, app
48
+ @server.mount '/', rack_handler, app
40
49
 
41
50
  Thread.new { @server.start }
42
51
  Timeout.timeout(1) { :wait until @started }
@@ -1,13 +1,33 @@
1
1
  require "flipper/cli"
2
2
 
3
3
  RSpec.describe Flipper::CLI do
4
+ let(:stdout) { StringIO.new }
5
+ let(:stderr) { StringIO.new }
6
+ let(:cli) { Flipper::CLI.new(stdout: stdout, stderr: stderr) }
7
+
8
+ before do
9
+ # Prentend stdout/stderr a TTY to test colorization
10
+ allow(stdout).to receive(:tty?).and_return(true)
11
+ allow(stderr).to receive(:tty?).and_return(true)
12
+ end
13
+
4
14
  # Infer the command from the description
5
15
  subject(:argv) do
6
16
  descriptions = self.class.parent_groups.map {|g| g.metadata[:description_args] }.reverse.flatten.drop(1)
7
17
  descriptions.map { |arg| Shellwords.split(arg) }.flatten
8
18
  end
9
19
 
10
- subject { run argv }
20
+ subject do
21
+ status = 0
22
+
23
+ begin
24
+ cli.run(argv)
25
+ rescue SystemExit => e
26
+ status = e.status
27
+ end
28
+
29
+ OpenStruct.new(status: status, stdout: stdout.string, stderr: stderr.string)
30
+ end
11
31
 
12
32
  before do
13
33
  ENV["FLIPPER_REQUIRE"] = "./spec/fixtures/environment"
@@ -141,49 +161,4 @@ RSpec.describe Flipper::CLI do
141
161
  it { should have_attributes(status: 0, stdout: /enabled.*admins/m) }
142
162
  end
143
163
  end
144
-
145
- context "bundler is not installed" do
146
- let(:argv) { "list" }
147
-
148
- around do |example|
149
- original_bundler = Bundler
150
- begin
151
- Object.send(:remove_const, :Bundler)
152
- example.run
153
- ensure
154
- Object.const_set(:Bundler, original_bundler)
155
- end
156
- end
157
-
158
- it "should not raise an error" do
159
- Flipper.enable(:enabled_feature)
160
- Flipper.enable_group(:enabled_groups, :admins)
161
- Flipper.add(:disabled_feature)
162
-
163
- expect(subject).to have_attributes(status: 0, stdout: /enabled_feature.*enabled_groups.*disabled_feature/m)
164
- end
165
- end
166
-
167
- def run(argv)
168
- original_stdout = $stdout
169
- original_stderr = $stderr
170
-
171
- $stdout = StringIO.new
172
- $stderr = StringIO.new
173
- status = 0
174
-
175
- # Prentend this a TTY so we can test colorization
176
- allow($stdout).to receive(:tty?).and_return(true)
177
-
178
- begin
179
- Flipper::CLI.run(argv)
180
- rescue SystemExit => e
181
- status = e.status
182
- end
183
-
184
- OpenStruct.new(status: status, stdout: $stdout.string, stderr: $stderr.string)
185
- ensure
186
- $stdout = original_stdout
187
- $stderr = original_stderr
188
- end
189
164
  end
@@ -20,7 +20,8 @@ RSpec.describe Flipper::Cloud::Configuration do
20
20
  it "can set instrumenter" do
21
21
  instrumenter = Object.new
22
22
  instance = described_class.new(required_options.merge(instrumenter: instrumenter))
23
- expect(instance.instrumenter).to be(instrumenter)
23
+ expect(instance.instrumenter).to be_a(Flipper::Cloud::Telemetry::Instrumenter)
24
+ expect(instance.instrumenter.instrumenter).to be(instrumenter)
24
25
  end
25
26
 
26
27
  it "can set read_timeout" do
@@ -2,6 +2,12 @@ require 'flipper/cloud/telemetry'
2
2
  require 'flipper/cloud/configuration'
3
3
 
4
4
  RSpec.describe Flipper::Cloud::Telemetry do
5
+ before do
6
+ # Stub polling for features.
7
+ stub_request(:get, "https://www.flippercloud.io/adapter/features?exclude_gate_names=true").
8
+ to_return(status: 200, body: "{}")
9
+ end
10
+
5
11
  it "phones home and does not update telemetry interval if missing" do
6
12
  stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
7
13
  to_return(status: 200, body: "{}")
@@ -42,6 +48,52 @@ RSpec.describe Flipper::Cloud::Telemetry do
42
48
  expect(stub).to have_been_requested
43
49
  end
44
50
 
51
+ it "phones home and requests shutdown if telemetry-shutdown header is true" do
52
+ stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
53
+ to_return(status: 404, body: "{}", headers: {"telemetry-shutdown" => "true"})
54
+
55
+ output = StringIO.new
56
+ cloud_configuration = Flipper::Cloud::Configuration.new(
57
+ token: "test",
58
+ logger: Logger.new(output),
59
+ logging_enabled: true,
60
+ )
61
+
62
+ # Record some telemetry and stop the threads so we submit a response.
63
+ telemetry = described_class.new(cloud_configuration)
64
+ telemetry.record(Flipper::Feature::InstrumentationName, {
65
+ operation: :enabled?,
66
+ feature_name: :foo,
67
+ result: true,
68
+ })
69
+ telemetry.stop
70
+ expect(stub).to have_been_requested
71
+ expect(output.string).to match(/action=telemetry_shutdown message=The server has requested that telemetry be shut down./)
72
+ end
73
+
74
+ it "phones home and does not shutdown if telemetry shutdown header is missing" do
75
+ stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
76
+ to_return(status: 404, body: "{}", headers: {})
77
+
78
+ output = StringIO.new
79
+ cloud_configuration = Flipper::Cloud::Configuration.new(
80
+ token: "test",
81
+ logger: Logger.new(output),
82
+ logging_enabled: true,
83
+ )
84
+
85
+ # Record some telemetry and stop the threads so we submit a response.
86
+ telemetry = described_class.new(cloud_configuration)
87
+ telemetry.record(Flipper::Feature::InstrumentationName, {
88
+ operation: :enabled?,
89
+ feature_name: :foo,
90
+ result: true,
91
+ })
92
+ telemetry.stop
93
+ expect(stub).to have_been_requested
94
+ expect(output.string).not_to match(/action=telemetry_shutdown message=The server has requested that telemetry be shut down./)
95
+ end
96
+
45
97
  it "can update telemetry interval from error" do
46
98
  stub = stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
47
99
  to_return(status: 500, body: "{}", headers: {"telemetry-interval" => "120"})
@@ -36,7 +36,8 @@ RSpec.describe Flipper::Cloud do
36
36
  expect(client.uri.host).to eq('www.flippercloud.io')
37
37
  expect(client.uri.path).to eq('/adapter')
38
38
  expect(client.headers["flipper-cloud-token"]).to eq(token)
39
- expect(@instance.instrumenter).to be(Flipper::Instrumenters::Noop)
39
+ expect(@instance.instrumenter).to be_a(Flipper::Cloud::Telemetry::Instrumenter)
40
+ expect(@instance.instrumenter.instrumenter).to be(Flipper::Instrumenters::Noop)
40
41
  end
41
42
  end
42
43
 
@@ -62,7 +63,8 @@ RSpec.describe Flipper::Cloud do
62
63
  it 'can set instrumenter' do
63
64
  instrumenter = Flipper::Instrumenters::Memory.new
64
65
  instance = described_class.new(token: 'asdf', instrumenter: instrumenter)
65
- expect(instance.instrumenter).to be(instrumenter)
66
+ expect(instance.instrumenter).to be_a(Flipper::Cloud::Telemetry::Instrumenter)
67
+ expect(instance.instrumenter.instrumenter).to be(instrumenter)
66
68
  end
67
69
 
68
70
  it 'allows wrapping adapter with another adapter like the instrumenter' do