flipper-cloud 0.20.0 → 0.21.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d17e92045e80beb6c0c83aa2591b75585393d0e6e922b834379fcfa0f5dc9049
4
- data.tar.gz: 6943dde37bab5cadf5e8e8a2db847aa6ddb0e310fd1b7bc9c5638835ae81cfe1
3
+ metadata.gz: 9ba3c2db9aa7175de2301fb4fea82a25164e935b0f0fe5d7ce38911eee319cf9
4
+ data.tar.gz: e97f8ec07dcc20057d6934e1cf933be9517d53549317539900630e43a7ffd23e
5
5
  SHA512:
6
- metadata.gz: cf7073018558fa53b8f7ada5d984b42066d0a5ae5cd81e343cd249cc2d1032ecd4c95a094c2f49027f1bf87de7ddc6c7f1a5804c5658d4700c09f0c0c7d74b8c
7
- data.tar.gz: faa00d96aae37eff0559f88e8a913ff407526825b55eff2404c794b2a6aa7f1f9882d04022746468fc157c52f8cc4eafa2ffc500c11aaa213876b38d963d6cab
6
+ metadata.gz: 62d4f0cc6817cfa654e1980156ca7e4d9ba81d1c0bb761f37651455b20f0b4732ceafe1adbf5a30ca09f8c1f8b80b8609c9d61ef969b82be48eedd183e4a5c15
7
+ data.tar.gz: 4540506c2b1c4eabe0140f3757e0414977ef876454755106bedfffd8159a880ffea37b6bca24bde30e9e6b79259ad86e30afb415219f7a22104c91bcd4edfbe8
Binary file
@@ -1,14 +1,9 @@
1
1
  # Usage (from the repo root):
2
- # env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> FLIPPER_CLOUD_SYNC_METHOD=webhook bundle exec rackup examples/ui/basic.ru -p 9999
3
- # env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> FLIPPER_CLOUD_SYNC_METHOD=webhook bundle exec shotgun examples/ui/basic.ru -p 9999
2
+ # env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> bundle exec rackup examples/cloud/app.ru -p 9999
3
+ # env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> bundle exec shotgun examples/cloud/app.ru -p 9999
4
4
  # http://localhost:9999/
5
- # http://localhost:9999/webhooks
6
-
7
- require 'pathname'
8
- root_path = Pathname(__FILE__).dirname.join('..').expand_path
9
- lib_path = root_path.join('lib')
10
- $:.unshift(lib_path)
11
5
 
6
+ require 'bundler/setup'
12
7
  require 'flipper/cloud'
13
8
  Flipper.configure do |config|
14
9
  config.default { Flipper::Cloud.new }
@@ -1,13 +1,8 @@
1
1
  # Usage (from the repo root):
2
- # env TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
3
- require 'pathname'
4
- require 'logger'
5
- root_path = Pathname(__FILE__).dirname.join('..').expand_path
6
- lib_path = root_path.join('lib')
7
- $:.unshift(lib_path)
8
-
2
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
3
+ require 'bundler/setup'
9
4
  require 'flipper/cloud'
10
- flipper = Flipper::Cloud.new(ENV.fetch('TOKEN'))
5
+ flipper = Flipper::Cloud.new
11
6
 
12
7
  flipper[:stats].enable
13
8
 
@@ -1,16 +1,15 @@
1
- require File.expand_path('../../example_setup', __FILE__)
2
-
1
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/cached_in_memory.rb
2
+ require 'bundler/setup'
3
3
  require 'flipper/cloud'
4
4
  require 'flipper/adapters/active_support_cache_store'
5
5
  require 'active_support/cache'
6
6
  require 'active_support/cache/memory_store'
7
7
 
8
- token = ENV.fetch("TOKEN") { abort "TOKEN environment variable not set." }
9
8
  feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
10
9
 
11
10
  Flipper.configure do |config|
12
11
  config.default do
13
- Flipper::Cloud.new(token) do |cloud|
12
+ Flipper::Cloud.new do |cloud|
14
13
  cloud.debug_output = STDOUT
15
14
  cloud.adapter do |adapter|
16
15
  Flipper::Adapters::ActiveSupportCacheStore.new(adapter,
@@ -1,23 +1,15 @@
1
1
  # Usage (from the repo root):
2
- # env TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
3
- require 'pathname'
4
- require 'logger'
5
- root_path = Pathname(__FILE__).dirname.join('..').expand_path
6
- lib_path = root_path.join('lib')
7
- $:.unshift(lib_path)
8
-
2
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/import.rb
3
+ require 'bundler/setup'
9
4
  require 'flipper'
10
5
  require 'flipper/cloud'
11
6
 
12
- memory_adapter = Flipper::Adapters::Memory.new
13
- memory_flipper = Flipper.new(memory_adapter)
14
-
15
- memory_flipper.enable(:test)
16
- memory_flipper.enable(:search)
17
- memory_flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
18
- memory_flipper.enable_percentage_of_time(:logging, 5)
7
+ Flipper.enable(:test)
8
+ Flipper.enable(:search)
9
+ Flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
10
+ Flipper.enable_percentage_of_time(:logging, 5)
19
11
 
20
- flipper = Flipper::Cloud.new(ENV.fetch('TOKEN'))
12
+ cloud = Flipper::Cloud.new
21
13
 
22
- # wipes cloud clean and makes it identical to memory flipper
23
- flipper.import(memory_flipper)
14
+ # makes cloud identical to memory flipper
15
+ cloud.import(Flipper)
@@ -5,13 +5,12 @@
5
5
  # never raise. You could get a slow request every now and then if cloud is
6
6
  # unavailable, but we are hoping to fix that soon by doing the cloud update in a
7
7
  # background thread.
8
- require File.expand_path('../../example_setup', __FILE__)
9
-
8
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/local_adapter.rb
9
+ require 'bundler/setup'
10
10
  require 'logger'
11
11
  require 'flipper/cloud'
12
12
  require 'flipper/adapters/redis'
13
13
 
14
- token = ENV.fetch("TOKEN") { abort "TOKEN environment variable not set." }
15
14
  feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
16
15
 
17
16
  redis = Redis.new(logger: Logger.new(STDOUT))
@@ -19,7 +18,7 @@ redis.flushdb
19
18
 
20
19
  Flipper.configure do |config|
21
20
  config.default do
22
- Flipper::Cloud.new(token) do |cloud|
21
+ Flipper::Cloud.new do |cloud|
23
22
  cloud.debug_output = STDOUT
24
23
  cloud.local_adapter = Flipper::Adapters::Redis.new(redis)
25
24
  cloud.sync_interval = 10
@@ -1,3 +1,4 @@
1
+ require "socket"
1
2
  require "flipper/adapters/http"
2
3
  require "flipper/adapters/memory"
3
4
  require "flipper/adapters/dual_write"
@@ -12,6 +13,8 @@ module Flipper
12
13
  :webhook,
13
14
  ].freeze
14
15
 
16
+ DEFAULT_URL = "https://www.flippercloud.io/adapter".freeze
17
+
15
18
  # Public: The token corresponding to an environment on flippercloud.io.
16
19
  attr_accessor :token
17
20
 
@@ -56,10 +59,6 @@ module Flipper
56
59
  # the local in sync with cloud (default: 10).
57
60
  attr_accessor :sync_interval
58
61
 
59
- # Public: The method to be used for synchronizing your local flipper
60
- # adapter with cloud. (default: :poll, can also be :webhook).
61
- attr_reader :sync_method
62
-
63
62
  # Public: The secret used to verify if syncs in the middleware should
64
63
  # occur or not.
65
64
  attr_accessor :sync_secret
@@ -71,6 +70,11 @@ module Flipper
71
70
  raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token (e.g. Flipper::Cloud.new('token'))."
72
71
  end
73
72
 
73
+ if ENV["FLIPPER_CLOUD_SYNC_METHOD"]
74
+ warn "FLIPPER_CLOUD_SYNC_METHOD is deprecated and has no effect."
75
+ end
76
+ self.sync_method = options[:sync_method] if options[:sync_method]
77
+
74
78
  @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
75
79
  @read_timeout = options.fetch(:read_timeout) { ENV.fetch("FLIPPER_CLOUD_READ_TIMEOUT", 5).to_f }
76
80
  @open_timeout = options.fetch(:open_timeout) { ENV.fetch("FLIPPER_CLOUD_OPEN_TIMEOUT", 5).to_f }
@@ -80,8 +84,7 @@ module Flipper
80
84
  @local_adapter = options.fetch(:local_adapter) { Adapters::Memory.new }
81
85
  @debug_output = options[:debug_output]
82
86
  @adapter_block = ->(adapter) { adapter }
83
- self.sync_method = options.fetch(:sync_method) { ENV.fetch("FLIPPER_CLOUD_SYNC_METHOD", :poll).to_sym }
84
- self.url = options.fetch(:url) { ENV.fetch("FLIPPER_CLOUD_URL", "https://www.flippercloud.io/adapter".freeze) }
87
+ self.url = options.fetch(:url) { ENV.fetch("FLIPPER_CLOUD_URL", DEFAULT_URL) }
85
88
  end
86
89
 
87
90
  # Public: Read or customize the http adapter. Calling without a block will
@@ -113,18 +116,14 @@ module Flipper
113
116
  }).call
114
117
  end
115
118
 
116
- def sync_method=(new_sync_method)
117
- new_sync_method = new_sync_method.to_sym
118
-
119
- unless VALID_SYNC_METHODS.include?(new_sync_method)
120
- raise ArgumentError, "Unsupported sync_method. Valid options are (#{VALID_SYNC_METHODS.to_a.join(', ')})"
121
- end
122
-
123
- if new_sync_method == :webhook && sync_secret.nil?
124
- raise ArgumentError, "Flipper::Cloud sync_secret is missing. Please set FLIPPER_CLOUD_SYNC_SECRET or provide the sync_secret used to validate webhooks."
125
- end
119
+ # Public: The method that will be used to synchronize local adapter with
120
+ # cloud. (default: :poll, will be :webhook if sync_secret is set).
121
+ def sync_method
122
+ sync_secret ? :webhook : :poll
123
+ end
126
124
 
127
- @sync_method = new_sync_method
125
+ def sync_method=(_)
126
+ warn "Flipper::Cloud: sync_method is deprecated and has no effect."
128
127
  end
129
128
 
130
129
  private
@@ -153,6 +152,11 @@ module Flipper
153
152
  headers: {
154
153
  "Flipper-Cloud-Token" => @token,
155
154
  "Feature-Flipper-Token" => @token,
155
+ "Client-Lang" => "ruby",
156
+ "Client-Lang-Version" => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
157
+ "Client-Platform" => RUBY_PLATFORM,
158
+ "Client-Engine" => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
159
+ "Client-Hostname" => Socket.gethostname,
156
160
  },
157
161
  })
158
162
  end
@@ -7,6 +7,8 @@ module Flipper
7
7
  class Middleware
8
8
  # Internal: The path to match for webhook requests.
9
9
  WEBHOOK_PATH = %r{\A/webhooks\/?\Z}
10
+ # Internal: The root path to match for requests.
11
+ ROOT_PATH = %r{\A/\Z}
10
12
 
11
13
  def initialize(app, options = {})
12
14
  @app = app
@@ -19,7 +21,7 @@ module Flipper
19
21
 
20
22
  def call!(env)
21
23
  request = Rack::Request.new(env)
22
- if request.post? && request.path_info.match(WEBHOOK_PATH)
24
+ if request.post? && (request.path_info.match(ROOT_PATH) || request.path_info.match(WEBHOOK_PATH))
23
25
  status = 200
24
26
  headers = {
25
27
  "Content-Type" => "application/json",
@@ -32,10 +34,20 @@ module Flipper
32
34
  begin
33
35
  message_verifier = MessageVerifier.new(secret: flipper.sync_secret)
34
36
  if message_verifier.verify(payload, signature)
35
- flipper.sync
36
- body = JSON.generate({
37
- groups: Flipper.group_names.map { |name| {name: name}}
38
- })
37
+ begin
38
+ flipper.sync
39
+ body = JSON.generate({
40
+ groups: Flipper.group_names.map { |name| {name: name}}
41
+ })
42
+ rescue Flipper::Adapters::Http::Error => error
43
+ status = error.response.code.to_i == 402 ? 402 : 500
44
+ headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
45
+ headers["Flipper-Cloud-Response-Error-Message"] = error.message
46
+ rescue => error
47
+ status = 500
48
+ headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
49
+ headers["Flipper-Cloud-Response-Error-Message"] = error.message
50
+ end
39
51
  end
40
52
  rescue MessageVerifier::InvalidSignature
41
53
  status = 400
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.20.0'.freeze
2
+ VERSION = '0.21.0.rc1'.freeze
3
3
  end
@@ -127,53 +127,30 @@ RSpec.describe Flipper::Cloud::Configuration do
127
127
  end
128
128
  end
129
129
 
130
- it "defaults to sync_method to poll" do
131
- memory_adapter = Flipper::Adapters::Memory.new
130
+ it "defaults sync_method to :poll" do
132
131
  instance = described_class.new(required_options)
133
132
 
134
133
  expect(instance.sync_method).to eq(:poll)
135
134
  end
136
135
 
137
- it "can use webhook for sync_method" do
138
- memory_adapter = Flipper::Adapters::Memory.new
136
+ it "sets sync_method to :webhook if sync_secret provided" do
139
137
  instance = described_class.new(required_options.merge({
140
138
  sync_secret: "secret",
141
- sync_method: :webhook,
142
- local_adapter: memory_adapter,
143
139
  }))
144
140
 
145
141
  expect(instance.sync_method).to eq(:webhook)
146
142
  expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
147
143
  end
148
144
 
149
- it "raises ArgumentError for invalid sync_method" do
150
- expect {
151
- described_class.new(required_options.merge(sync_method: :foo))
152
- }.to raise_error(ArgumentError, "Unsupported sync_method. Valid options are (poll, webhook)")
153
- end
154
-
155
- it "can use ENV var for sync_method" do
156
- with_modified_env "FLIPPER_CLOUD_SYNC_METHOD" => "webhook" do
157
- instance = described_class.new(required_options.merge({
158
- sync_secret: "secret",
159
- }))
145
+ it "sets sync_method to :webhook if FLIPPER_CLOUD_SYNC_SECRET set" do
146
+ with_modified_env "FLIPPER_CLOUD_SYNC_SECRET" => "abc" do
147
+ instance = described_class.new(required_options)
160
148
 
161
149
  expect(instance.sync_method).to eq(:webhook)
150
+ expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
162
151
  end
163
152
  end
164
153
 
165
- it "can use string sync_method instead of symbol" do
166
- memory_adapter = Flipper::Adapters::Memory.new
167
- instance = described_class.new(required_options.merge({
168
- sync_secret: "secret",
169
- sync_method: "webhook",
170
- local_adapter: memory_adapter,
171
- }))
172
-
173
- expect(instance.sync_method).to eq(:webhook)
174
- expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
175
- end
176
-
177
154
  it "can set sync_secret" do
178
155
  instance = described_class.new(required_options.merge(sync_secret: "from_config"))
179
156
  expect(instance.sync_secret).to eq("from_config")
@@ -9,7 +9,6 @@ RSpec.describe Flipper::Cloud::DSL do
9
9
  cloud_configuration = Flipper::Cloud::Configuration.new({
10
10
  token: "asdf",
11
11
  sync_secret: "tasty",
12
- sync_method: :webhook,
13
12
  })
14
13
  dsl = described_class.new(cloud_configuration)
15
14
  expect(dsl.features).to eq(Set.new)
@@ -26,7 +25,6 @@ RSpec.describe Flipper::Cloud::DSL do
26
25
  cloud_configuration = Flipper::Cloud::Configuration.new({
27
26
  token: "asdf",
28
27
  sync_secret: "tasty",
29
- sync_method: :webhook,
30
28
  })
31
29
  dsl = described_class.new(cloud_configuration)
32
30
  dsl.sync
@@ -37,7 +35,6 @@ RSpec.describe Flipper::Cloud::DSL do
37
35
  cloud_configuration = Flipper::Cloud::Configuration.new({
38
36
  token: "asdf",
39
37
  sync_secret: "tasty",
40
- sync_method: :webhook,
41
38
  })
42
39
  dsl = described_class.new(cloud_configuration)
43
40
  expect(dsl.sync_secret).to eq("tasty")
@@ -52,7 +49,6 @@ RSpec.describe Flipper::Cloud::DSL do
52
49
  cloud_configuration = Flipper::Cloud::Configuration.new({
53
50
  token: "asdf",
54
51
  sync_secret: "tasty",
55
- sync_method: :webhook,
56
52
  local_adapter: local_adapter
57
53
  })
58
54
  end
@@ -9,7 +9,6 @@ RSpec.describe Flipper::Cloud::Middleware do
9
9
  Flipper::Cloud.new("regular") do |config|
10
10
  config.local_adapter = Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
11
11
  config.sync_secret = "regular_tasty"
12
- config.sync_method = :webhook
13
12
  end
14
13
  }
15
14
 
@@ -17,7 +16,6 @@ RSpec.describe Flipper::Cloud::Middleware do
17
16
  Flipper::Cloud.new("env") do |config|
18
17
  config.local_adapter = Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
19
18
  config.sync_secret = "env_tasty"
20
- config.sync_method = :webhook
21
19
  end
22
20
  }
23
21
 
@@ -53,7 +51,7 @@ RSpec.describe Flipper::Cloud::Middleware do
53
51
  env = {
54
52
  "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
55
53
  }
56
- post '/webhooks', request_body, env
54
+ post '/', request_body, env
57
55
 
58
56
  expect(last_response.status).to eq(200)
59
57
  expect(JSON.parse(last_response.body)).to eq({
@@ -80,13 +78,82 @@ RSpec.describe Flipper::Cloud::Middleware do
80
78
  env = {
81
79
  "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
82
80
  }
83
- post '/webhooks', request_body, env
81
+ post '/', request_body, env
84
82
 
85
83
  expect(last_response.status).to eq(400)
86
84
  expect(stub).not_to have_been_requested
87
85
  end
88
86
  end
89
87
 
88
+ context "when flipper cloud responds with 402" do
89
+ let(:app) { Flipper::Cloud.app(flipper) }
90
+
91
+ it "results in 402" do
92
+ Flipper.register(:admins) { |*args| false }
93
+ Flipper.register(:staff) { |*args| false }
94
+ Flipper.register(:basic) { |*args| false }
95
+ Flipper.register(:plus) { |*args| false }
96
+ Flipper.register(:premium) { |*args| false }
97
+
98
+ stub = stub_request_for_token('regular', status: 402)
99
+ env = {
100
+ "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
101
+ }
102
+ post '/', request_body, env
103
+
104
+ expect(last_response.status).to eq(402)
105
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
106
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to include("Failed with status: 402")
107
+ expect(stub).to have_been_requested
108
+ end
109
+ end
110
+
111
+ context "when flipper cloud responds with non-402 and non-2xx code" do
112
+ let(:app) { Flipper::Cloud.app(flipper) }
113
+
114
+ it "results in 500" do
115
+ Flipper.register(:admins) { |*args| false }
116
+ Flipper.register(:staff) { |*args| false }
117
+ Flipper.register(:basic) { |*args| false }
118
+ Flipper.register(:plus) { |*args| false }
119
+ Flipper.register(:premium) { |*args| false }
120
+
121
+ stub = stub_request_for_token('regular', status: 503)
122
+ env = {
123
+ "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
124
+ }
125
+ post '/', request_body, env
126
+
127
+ expect(last_response.status).to eq(500)
128
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
129
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to include("Failed with status: 503")
130
+ expect(stub).to have_been_requested
131
+ end
132
+ end
133
+
134
+ context "when flipper cloud responds with timeout" do
135
+ let(:app) { Flipper::Cloud.app(flipper) }
136
+
137
+ it "results in 500" do
138
+ Flipper.register(:admins) { |*args| false }
139
+ Flipper.register(:staff) { |*args| false }
140
+ Flipper.register(:basic) { |*args| false }
141
+ Flipper.register(:plus) { |*args| false }
142
+ Flipper.register(:premium) { |*args| false }
143
+
144
+ stub = stub_request_for_token('regular', status: :timeout)
145
+ env = {
146
+ "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
147
+ }
148
+ post '/', request_body, env
149
+
150
+ expect(last_response.status).to eq(500)
151
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Net::OpenTimeout")
152
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("execution expired")
153
+ expect(stub).to have_been_requested
154
+ end
155
+ end
156
+
90
157
  context 'when initialized with flipper instance and flipper instance in env' do
91
158
  let(:app) { Flipper::Cloud.app(flipper) }
92
159
  let(:signature) {
@@ -99,7 +166,7 @@ RSpec.describe Flipper::Cloud::Middleware do
99
166
  "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
100
167
  'flipper' => env_flipper,
101
168
  }
102
- post '/webhooks', request_body, env
169
+ post '/', request_body, env
103
170
 
104
171
  expect(last_response.status).to eq(200)
105
172
  expect(stub).to have_been_requested
@@ -118,7 +185,7 @@ RSpec.describe Flipper::Cloud::Middleware do
118
185
  "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
119
186
  'flipper' => env_flipper,
120
187
  }
121
- post '/webhooks', request_body, env
188
+ post '/', request_body, env
122
189
 
123
190
  expect(last_response.status).to eq(200)
124
191
  expect(stub).to have_been_requested
@@ -138,7 +205,7 @@ RSpec.describe Flipper::Cloud::Middleware do
138
205
  'flipper' => flipper,
139
206
  'flipper_cloud' => env_flipper,
140
207
  }
141
- post '/webhooks', request_body, env
208
+ post '/', request_body, env
142
209
 
143
210
  expect(last_response.status).to eq(200)
144
211
  expect(stub).to have_been_requested
@@ -149,6 +216,27 @@ RSpec.describe Flipper::Cloud::Middleware do
149
216
  let(:app) { Flipper::Cloud.app(-> { flipper }) }
150
217
 
151
218
  it 'works' do
219
+ stub = stub_request_for_token('regular')
220
+ env = {
221
+ "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
222
+ }
223
+ post '/', request_body, env
224
+
225
+ expect(last_response.status).to eq(200)
226
+ expect(stub).to have_been_requested
227
+ end
228
+ end
229
+
230
+ context 'when using older /webhooks path' do
231
+ let(:app) { Flipper::Cloud.app(flipper) }
232
+
233
+ it 'uses instance to sync' do
234
+ Flipper.register(:admins) { |*args| false }
235
+ Flipper.register(:staff) { |*args| false }
236
+ Flipper.register(:basic) { |*args| false }
237
+ Flipper.register(:plus) { |*args| false }
238
+ Flipper.register(:premium) { |*args| false }
239
+
152
240
  stub = stub_request_for_token('regular')
153
241
  env = {
154
242
  "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
@@ -156,13 +244,22 @@ RSpec.describe Flipper::Cloud::Middleware do
156
244
  post '/webhooks', request_body, env
157
245
 
158
246
  expect(last_response.status).to eq(200)
247
+ expect(JSON.parse(last_response.body)).to eq({
248
+ "groups" => [
249
+ {"name" => "admins"},
250
+ {"name" => "staff"},
251
+ {"name" => "basic"},
252
+ {"name" => "plus"},
253
+ {"name" => "premium"},
254
+ ],
255
+ })
159
256
  expect(stub).to have_been_requested
160
257
  end
161
258
  end
162
259
 
163
260
  describe 'Request method unsupported' do
164
261
  it 'skips middleware' do
165
- get '/webhooks'
262
+ get '/'
166
263
  expect(last_response.status).to eq(404)
167
264
  expect(last_response.content_type).to eq("application/json")
168
265
  expect(last_response.body).to eq("{}")
@@ -177,12 +274,17 @@ RSpec.describe Flipper::Cloud::Middleware do
177
274
 
178
275
  private
179
276
 
180
- def stub_request_for_token(token)
181
- stub_request(:get, "https://www.flippercloud.io/adapter/features").
277
+ def stub_request_for_token(token, status: 200)
278
+ stub = stub_request(:get, "https://www.flippercloud.io/adapter/features").
182
279
  with({
183
280
  headers: {
184
281
  'Flipper-Cloud-Token' => token,
185
282
  },
186
- }).to_return(status: 200, body: response_body, headers: {})
283
+ })
284
+ if status == :timeout
285
+ stub.to_timeout
286
+ else
287
+ stub.to_return(status: status, body: response_body, headers: {})
288
+ end
187
289
  end
188
290
  end
@@ -114,4 +114,89 @@ RSpec.describe Flipper::Cloud do
114
114
  described_class.new('asdf', open_timeout: 1)
115
115
  end
116
116
  end
117
+
118
+ it 'can import' do
119
+ stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
120
+ with(headers: {
121
+ 'Feature-Flipper-Token'=>'asdf',
122
+ 'Flipper-Cloud-Token'=>'asdf',
123
+ }).to_return(status: 200, body: "{}", headers: {})
124
+
125
+ flipper = Flipper.new(Flipper::Adapters::Memory.new)
126
+
127
+ flipper.enable(:test)
128
+ flipper.enable(:search)
129
+ flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
130
+ flipper.enable_percentage_of_time(:logging, 5)
131
+
132
+ cloud_flipper = Flipper::Cloud.new("asdf")
133
+
134
+ get_all = {
135
+ "logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
136
+ "search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
137
+ "stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
138
+ "test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
139
+ }
140
+
141
+ expect(flipper.adapter.get_all).to eq(get_all)
142
+ cloud_flipper.import(flipper)
143
+ expect(flipper.adapter.get_all).to eq(get_all)
144
+ expect(cloud_flipper.adapter.get_all).to eq(get_all)
145
+ end
146
+
147
+ it 'raises error for failure while importing' do
148
+ stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
149
+ with(headers: {
150
+ 'Feature-Flipper-Token'=>'asdf',
151
+ 'Flipper-Cloud-Token'=>'asdf',
152
+ }).to_return(status: 500, body: "{}")
153
+
154
+ flipper = Flipper.new(Flipper::Adapters::Memory.new)
155
+
156
+ flipper.enable(:test)
157
+ flipper.enable(:search)
158
+ flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
159
+ flipper.enable_percentage_of_time(:logging, 5)
160
+
161
+ cloud_flipper = Flipper::Cloud.new("asdf")
162
+
163
+ get_all = {
164
+ "logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
165
+ "search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
166
+ "stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
167
+ "test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
168
+ }
169
+
170
+ expect(flipper.adapter.get_all).to eq(get_all)
171
+ expect { cloud_flipper.import(flipper) }.to raise_error(Flipper::Adapters::Http::Error)
172
+ expect(flipper.adapter.get_all).to eq(get_all)
173
+ end
174
+
175
+ it 'raises error for timeout while importing' do
176
+ stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
177
+ with(headers: {
178
+ 'Feature-Flipper-Token'=>'asdf',
179
+ 'Flipper-Cloud-Token'=>'asdf',
180
+ }).to_timeout
181
+
182
+ flipper = Flipper.new(Flipper::Adapters::Memory.new)
183
+
184
+ flipper.enable(:test)
185
+ flipper.enable(:search)
186
+ flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
187
+ flipper.enable_percentage_of_time(:logging, 5)
188
+
189
+ cloud_flipper = Flipper::Cloud.new("asdf")
190
+
191
+ get_all = {
192
+ "logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
193
+ "search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
194
+ "stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
195
+ "test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
196
+ }
197
+
198
+ expect(flipper.adapter.get_all).to eq(get_all)
199
+ expect { cloud_flipper.import(flipper) }.to raise_error(Net::OpenTimeout)
200
+ expect(flipper.adapter.get_all).to eq(get_all)
201
+ end
117
202
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.21.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-20 00:00:00.000000000 Z
11
+ date: 2021-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: flipper
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.20.0
19
+ version: 0.21.0.rc1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.20.0
26
+ version: 0.21.0.rc1
27
27
  description:
28
28
  email:
29
29
  - nunemaker@gmail.com
@@ -31,6 +31,7 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - docs/images/flipper_cloud.png
34
35
  - examples/cloud/app.ru
35
36
  - examples/cloud/basic.rb
36
37
  - examples/cloud/cached_in_memory.rb
@@ -65,9 +66,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
65
66
  version: '0'
66
67
  required_rubygems_version: !ruby/object:Gem::Requirement
67
68
  requirements:
68
- - - ">="
69
+ - - ">"
69
70
  - !ruby/object:Gem::Version
70
- version: '0'
71
+ version: 1.3.1
71
72
  requirements: []
72
73
  rubygems_version: 3.0.3
73
74
  signing_key: