flipper 0.16.0 → 1.4.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 (285) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +1 -0
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/dependabot.yml +6 -0
  5. data/.github/workflows/ci.yml +110 -0
  6. data/.github/workflows/examples.yml +105 -0
  7. data/.github/workflows/release.yml +54 -0
  8. data/.rspec +1 -0
  9. data/CLAUDE.md +87 -0
  10. data/Changelog.md +2 -215
  11. data/Dockerfile +1 -1
  12. data/Gemfile +28 -20
  13. data/README.md +72 -62
  14. data/Rakefile +13 -3
  15. data/benchmark/enabled_ips.rb +10 -0
  16. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  17. data/benchmark/enabled_profile.rb +20 -0
  18. data/benchmark/instrumentation_ips.rb +21 -0
  19. data/benchmark/typecast_ips.rb +27 -0
  20. data/docker-compose.yml +37 -34
  21. data/docs/DockerCompose.md +0 -1
  22. data/docs/README.md +1 -0
  23. data/docs/images/banner.jpg +0 -0
  24. data/docs/images/flipper_cloud.png +0 -0
  25. data/examples/api/basic.ru +18 -0
  26. data/examples/api/custom_memoized.ru +36 -0
  27. data/examples/api/memoized.ru +42 -0
  28. data/examples/basic.rb +1 -12
  29. data/examples/cloud/app.ru +12 -0
  30. data/examples/cloud/backoff_policy.rb +13 -0
  31. data/examples/cloud/basic.rb +22 -0
  32. data/examples/cloud/cloud_setup.rb +20 -0
  33. data/examples/cloud/forked.rb +36 -0
  34. data/examples/cloud/import.rb +17 -0
  35. data/examples/cloud/poll_interval/README.md +111 -0
  36. data/examples/cloud/poll_interval/client.rb +108 -0
  37. data/examples/cloud/poll_interval/server.rb +98 -0
  38. data/examples/cloud/threaded.rb +33 -0
  39. data/examples/configuring_default.rb +2 -5
  40. data/examples/dsl.rb +10 -35
  41. data/examples/enabled_for_actor.rb +10 -15
  42. data/examples/expressions.rb +237 -0
  43. data/examples/group.rb +3 -6
  44. data/examples/group_dynamic_lookup.rb +5 -19
  45. data/examples/group_with_members.rb +4 -14
  46. data/examples/importing.rb +1 -1
  47. data/examples/individual_actor.rb +2 -5
  48. data/examples/instrumentation.rb +2 -2
  49. data/examples/instrumentation_last_accessed_at.rb +38 -0
  50. data/examples/memoizing.rb +35 -0
  51. data/examples/mirroring.rb +59 -0
  52. data/examples/percentage_of_actors.rb +6 -16
  53. data/examples/percentage_of_actors_enabled_check.rb +7 -10
  54. data/examples/percentage_of_actors_group.rb +5 -18
  55. data/examples/percentage_of_time.rb +3 -6
  56. data/examples/strict.rb +18 -0
  57. data/exe/flipper +5 -0
  58. data/flipper-cloud.gemspec +19 -0
  59. data/flipper.gemspec +10 -7
  60. data/lib/flipper/actor.rb +10 -3
  61. data/lib/flipper/adapter.rb +50 -8
  62. data/lib/flipper/adapter_builder.rb +44 -0
  63. data/lib/flipper/adapters/actor_limit.rb +54 -0
  64. data/lib/flipper/adapters/cache_base.rb +161 -0
  65. data/lib/flipper/adapters/dual_write.rb +63 -0
  66. data/lib/flipper/adapters/failover.rb +85 -0
  67. data/lib/flipper/adapters/failsafe.rb +72 -0
  68. data/lib/flipper/adapters/http/client.rb +64 -7
  69. data/lib/flipper/adapters/http/error.rb +19 -1
  70. data/lib/flipper/adapters/http.rb +97 -43
  71. data/lib/flipper/adapters/instrumented.rb +47 -26
  72. data/lib/flipper/adapters/memoizable.rb +44 -40
  73. data/lib/flipper/adapters/memory.rb +75 -111
  74. data/lib/flipper/adapters/operation_logger.rb +22 -78
  75. data/lib/flipper/adapters/poll/poller.rb +2 -0
  76. data/lib/flipper/adapters/poll.rb +52 -0
  77. data/lib/flipper/adapters/pstore.rb +27 -17
  78. data/lib/flipper/adapters/read_only.rb +8 -41
  79. data/lib/flipper/adapters/strict.rb +45 -0
  80. data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
  81. data/lib/flipper/adapters/sync/interval_synchronizer.rb +2 -7
  82. data/lib/flipper/adapters/sync/synchronizer.rb +13 -6
  83. data/lib/flipper/adapters/sync.rb +23 -29
  84. data/lib/flipper/adapters/wrapper.rb +54 -0
  85. data/lib/flipper/cli.rb +314 -0
  86. data/lib/flipper/cloud/configuration.rb +271 -0
  87. data/lib/flipper/cloud/dsl.rb +27 -0
  88. data/lib/flipper/cloud/message_verifier.rb +95 -0
  89. data/lib/flipper/cloud/middleware.rb +63 -0
  90. data/lib/flipper/cloud/migrate.rb +71 -0
  91. data/lib/flipper/cloud/routes.rb +14 -0
  92. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  93. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  94. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  95. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  96. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  97. data/lib/flipper/cloud/telemetry.rb +191 -0
  98. data/lib/flipper/cloud.rb +54 -0
  99. data/lib/flipper/configuration.rb +54 -7
  100. data/lib/flipper/dsl.rb +58 -47
  101. data/lib/flipper/engine.rb +102 -0
  102. data/lib/flipper/errors.rb +3 -21
  103. data/lib/flipper/export.rb +24 -0
  104. data/lib/flipper/exporter.rb +17 -0
  105. data/lib/flipper/exporters/json/export.rb +32 -0
  106. data/lib/flipper/exporters/json/v1.rb +33 -0
  107. data/lib/flipper/expression/builder.rb +73 -0
  108. data/lib/flipper/expression/constant.rb +25 -0
  109. data/lib/flipper/expression.rb +71 -0
  110. data/lib/flipper/expressions/all.rb +9 -0
  111. data/lib/flipper/expressions/any.rb +9 -0
  112. data/lib/flipper/expressions/boolean.rb +9 -0
  113. data/lib/flipper/expressions/comparable.rb +13 -0
  114. data/lib/flipper/expressions/equal.rb +9 -0
  115. data/lib/flipper/expressions/feature_enabled.rb +34 -0
  116. data/lib/flipper/expressions/greater_than.rb +9 -0
  117. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  118. data/lib/flipper/expressions/less_than.rb +9 -0
  119. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  120. data/lib/flipper/expressions/not_equal.rb +9 -0
  121. data/lib/flipper/expressions/now.rb +9 -0
  122. data/lib/flipper/expressions/number.rb +9 -0
  123. data/lib/flipper/expressions/percentage.rb +9 -0
  124. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  125. data/lib/flipper/expressions/property.rb +9 -0
  126. data/lib/flipper/expressions/random.rb +9 -0
  127. data/lib/flipper/expressions/string.rb +9 -0
  128. data/lib/flipper/expressions/time.rb +16 -0
  129. data/lib/flipper/feature.rb +95 -28
  130. data/lib/flipper/feature_check_context.rb +10 -6
  131. data/lib/flipper/gate.rb +13 -11
  132. data/lib/flipper/gate_values.rb +5 -18
  133. data/lib/flipper/gates/actor.rb +10 -17
  134. data/lib/flipper/gates/boolean.rb +1 -1
  135. data/lib/flipper/gates/expression.rb +75 -0
  136. data/lib/flipper/gates/group.rb +5 -7
  137. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  138. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  139. data/lib/flipper/identifier.rb +17 -0
  140. data/lib/flipper/instrumentation/log_subscriber.rb +35 -8
  141. data/lib/flipper/instrumentation/statsd.rb +4 -2
  142. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  143. data/lib/flipper/instrumentation/subscriber.rb +8 -5
  144. data/lib/flipper/instrumenters/memory.rb +6 -2
  145. data/lib/flipper/metadata.rb +8 -1
  146. data/lib/flipper/middleware/memoizer.rb +46 -27
  147. data/lib/flipper/middleware/setup_env.rb +13 -3
  148. data/lib/flipper/model/active_record.rb +23 -0
  149. data/lib/flipper/poller.rb +157 -0
  150. data/lib/flipper/serializers/gzip.rb +22 -0
  151. data/lib/flipper/serializers/json.rb +17 -0
  152. data/lib/flipper/spec/shared_adapter_specs.rb +122 -56
  153. data/lib/flipper/test/shared_adapter_test.rb +120 -52
  154. data/lib/flipper/test_help.rb +43 -0
  155. data/lib/flipper/typecast.rb +59 -18
  156. data/lib/flipper/types/actor.rb +19 -13
  157. data/lib/flipper/types/group.rb +12 -5
  158. data/lib/flipper/types/percentage.rb +1 -1
  159. data/lib/flipper/version.rb +11 -1
  160. data/lib/flipper.rb +71 -12
  161. data/lib/generators/flipper/setup_generator.rb +68 -0
  162. data/lib/generators/flipper/templates/initializer.rb +45 -0
  163. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  164. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  165. data/lib/generators/flipper/update_generator.rb +35 -0
  166. data/package-lock.json +41 -0
  167. data/package.json +10 -0
  168. data/spec/fixtures/environment.rb +1 -0
  169. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  170. data/spec/flipper/actor_spec.rb +10 -2
  171. data/spec/flipper/adapter_builder_spec.rb +72 -0
  172. data/spec/flipper/adapter_spec.rb +52 -6
  173. data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
  174. data/spec/flipper/adapters/dual_write_spec.rb +82 -0
  175. data/spec/flipper/adapters/failover_spec.rb +141 -0
  176. data/spec/flipper/adapters/failsafe_spec.rb +58 -0
  177. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  178. data/spec/flipper/adapters/http_spec.rb +402 -65
  179. data/spec/flipper/adapters/instrumented_spec.rb +31 -13
  180. data/spec/flipper/adapters/memoizable_spec.rb +51 -33
  181. data/spec/flipper/adapters/memory_spec.rb +33 -5
  182. data/spec/flipper/adapters/operation_logger_spec.rb +38 -12
  183. data/spec/flipper/adapters/poll_spec.rb +41 -0
  184. data/spec/flipper/adapters/pstore_spec.rb +0 -2
  185. data/spec/flipper/adapters/read_only_spec.rb +32 -18
  186. data/spec/flipper/adapters/strict_spec.rb +64 -0
  187. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -1
  188. data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
  189. data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -1
  190. data/spec/flipper/adapters/sync_spec.rb +17 -6
  191. data/spec/flipper/cli_spec.rb +217 -0
  192. data/spec/flipper/cloud/configuration_spec.rb +257 -0
  193. data/spec/flipper/cloud/dsl_spec.rb +90 -0
  194. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  195. data/spec/flipper/cloud/middleware_spec.rb +307 -0
  196. data/spec/flipper/cloud/migrate_spec.rb +160 -0
  197. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  198. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  199. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  200. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  201. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  202. data/spec/flipper/cloud_spec.rb +186 -0
  203. data/spec/flipper/configuration_spec.rb +37 -3
  204. data/spec/flipper/dsl_spec.rb +67 -80
  205. data/spec/flipper/engine_spec.rb +374 -0
  206. data/spec/flipper/export_spec.rb +13 -0
  207. data/spec/flipper/exporter_spec.rb +16 -0
  208. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  209. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  210. data/spec/flipper/expression/builder_spec.rb +248 -0
  211. data/spec/flipper/expression_spec.rb +188 -0
  212. data/spec/flipper/expressions/all_spec.rb +15 -0
  213. data/spec/flipper/expressions/any_spec.rb +15 -0
  214. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  215. data/spec/flipper/expressions/equal_spec.rb +24 -0
  216. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  217. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  218. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  219. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  220. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  221. data/spec/flipper/expressions/now_spec.rb +11 -0
  222. data/spec/flipper/expressions/number_spec.rb +21 -0
  223. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  224. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  225. data/spec/flipper/expressions/property_spec.rb +13 -0
  226. data/spec/flipper/expressions/random_spec.rb +9 -0
  227. data/spec/flipper/expressions/string_spec.rb +11 -0
  228. data/spec/flipper/expressions/time_spec.rb +29 -0
  229. data/spec/flipper/feature_check_context_spec.rb +18 -20
  230. data/spec/flipper/feature_spec.rb +461 -48
  231. data/spec/flipper/gate_spec.rb +0 -2
  232. data/spec/flipper/gate_values_spec.rb +2 -34
  233. data/spec/flipper/gates/actor_spec.rb +0 -2
  234. data/spec/flipper/gates/boolean_spec.rb +1 -3
  235. data/spec/flipper/gates/expression_spec.rb +190 -0
  236. data/spec/flipper/gates/group_spec.rb +2 -5
  237. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
  238. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
  239. data/spec/flipper/identifier_spec.rb +12 -0
  240. data/spec/flipper/instrumentation/log_subscriber_spec.rb +24 -7
  241. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +26 -3
  242. data/spec/flipper/instrumenters/memory_spec.rb +18 -1
  243. data/spec/flipper/instrumenters/noop_spec.rb +14 -8
  244. data/spec/flipper/middleware/memoizer_spec.rb +199 -62
  245. data/spec/flipper/middleware/setup_env_spec.rb +23 -5
  246. data/spec/flipper/model/active_record_spec.rb +72 -0
  247. data/spec/flipper/poller_spec.rb +390 -0
  248. data/spec/flipper/registry_spec.rb +0 -1
  249. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  250. data/spec/flipper/serializers/json_spec.rb +13 -0
  251. data/spec/flipper/typecast_spec.rb +121 -7
  252. data/spec/flipper/types/actor_spec.rb +63 -47
  253. data/spec/flipper/types/boolean_spec.rb +0 -1
  254. data/spec/flipper/types/group_spec.rb +24 -3
  255. data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
  256. data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
  257. data/spec/flipper/types/percentage_spec.rb +0 -1
  258. data/spec/{integration_spec.rb → flipper_integration_spec.rb} +301 -59
  259. data/spec/flipper_spec.rb +123 -29
  260. data/spec/{helper.rb → spec_helper.rb} +23 -21
  261. data/spec/support/actor_names.yml +1 -0
  262. data/spec/support/descriptions.yml +1 -0
  263. data/spec/support/fail_on_output.rb +8 -0
  264. data/spec/support/fake_backoff_policy.rb +15 -0
  265. data/spec/support/skippable.rb +18 -0
  266. data/spec/support/spec_helpers.rb +53 -6
  267. data/test/adapters/actor_limit_test.rb +20 -0
  268. data/test/test_helper.rb +2 -1
  269. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  270. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  271. data/test_rails/helper.rb +31 -0
  272. data/test_rails/system/test_help_test.rb +52 -0
  273. metadata +200 -82
  274. data/.rubocop.yml +0 -54
  275. data/.rubocop_todo.yml +0 -199
  276. data/docs/Adapters.md +0 -124
  277. data/docs/Caveats.md +0 -4
  278. data/docs/Gates.md +0 -167
  279. data/docs/Instrumentation.md +0 -27
  280. data/docs/Optimization.md +0 -114
  281. data/docs/api/README.md +0 -849
  282. data/docs/http/README.md +0 -35
  283. data/docs/read-only/README.md +0 -21
  284. data/examples/example_setup.rb +0 -8
  285. data/test/helper.rb +0 -11
@@ -1,10 +1,8 @@
1
- require 'helper'
2
1
  require 'flipper/adapters/memoizable'
3
2
  require 'flipper/adapters/operation_logger'
4
- require 'flipper/spec/shared_adapter_specs'
5
3
 
6
4
  RSpec.describe Flipper::Adapters::Memoizable do
7
- let(:features_key) { described_class::FeaturesKey }
5
+ let(:features_key) { :flipper_features }
8
6
  let(:adapter) { Flipper::Adapters::Memory.new }
9
7
  let(:flipper) { Flipper.new(adapter) }
10
8
  let(:cache) { {} }
@@ -13,16 +11,6 @@ RSpec.describe Flipper::Adapters::Memoizable do
13
11
 
14
12
  it_should_behave_like 'a flipper adapter'
15
13
 
16
- it 'forwards missing methods to underlying adapter' do
17
- adapter = Class.new do
18
- def foo
19
- :foo
20
- end
21
- end.new
22
- memoizable = described_class.new(adapter)
23
- expect(memoizable.foo).to eq(:foo)
24
- end
25
-
26
14
  describe '#name' do
27
15
  it 'is instrumented' do
28
16
  expect(subject.name).to be(:memoizable)
@@ -66,7 +54,7 @@ RSpec.describe Flipper::Adapters::Memoizable do
66
54
  it 'memoizes feature' do
67
55
  feature = flipper[:stats]
68
56
  result = subject.get(feature)
69
- expect(cache[described_class.key_for(feature.key)]).to be(result)
57
+ expect(cache["feature/#{feature.key}"]).to be(result)
70
58
  end
71
59
  end
72
60
 
@@ -95,8 +83,8 @@ RSpec.describe Flipper::Adapters::Memoizable do
95
83
  features = names.map { |name| flipper[name] }
96
84
  results = subject.get_multi(features)
97
85
  features.each do |feature|
98
- expect(cache[described_class.key_for(feature.key)]).not_to be(nil)
99
- expect(cache[described_class.key_for(feature.key)]).to be(results[feature.key])
86
+ expect(cache["feature/#{feature.key}"]).not_to be(nil)
87
+ expect(cache["feature/#{feature.key}"]).to be(results[feature.key])
100
88
  end
101
89
  end
102
90
  end
@@ -127,10 +115,10 @@ RSpec.describe Flipper::Adapters::Memoizable do
127
115
  features = names.map { |name| flipper[name].tap(&:enable) }
128
116
  results = subject.get_all
129
117
  features.each do |feature|
130
- expect(cache[described_class.key_for(feature.key)]).not_to be(nil)
131
- expect(cache[described_class.key_for(feature.key)]).to be(results[feature.key])
118
+ expect(cache["feature/#{feature.key}"]).not_to be(nil)
119
+ expect(cache["feature/#{feature.key}"]).to be(results[feature.key])
132
120
  end
133
- expect(cache[subject.class::FeaturesKey]).to eq(names.map(&:to_s).to_set)
121
+ expect(cache[:flipper_features]).to eq(names.map(&:to_s).to_set)
134
122
  end
135
123
 
136
124
  it 'only calls get_all once for memoized adapter' do
@@ -200,9 +188,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
200
188
  it 'unmemoizes feature' do
201
189
  feature = flipper[:stats]
202
190
  gate = feature.gate(:boolean)
203
- cache[described_class.key_for(feature.key)] = { some: 'thing' }
204
- subject.enable(feature, gate, flipper.bool)
205
- expect(cache[described_class.key_for(feature.key)]).to be_nil
191
+ cache["feature/#{feature.key}"] = { some: 'thing' }
192
+ subject.enable(feature, gate, Flipper::Types::Boolean.new)
193
+ expect(cache["feature/#{feature.key}"]).to be_nil
206
194
  end
207
195
  end
208
196
 
@@ -214,8 +202,8 @@ RSpec.describe Flipper::Adapters::Memoizable do
214
202
  it 'returns result' do
215
203
  feature = flipper[:stats]
216
204
  gate = feature.gate(:boolean)
217
- result = subject.enable(feature, gate, flipper.bool)
218
- adapter_result = adapter.enable(feature, gate, flipper.bool)
205
+ result = subject.enable(feature, gate, Flipper::Types::Boolean.new)
206
+ adapter_result = adapter.enable(feature, gate, Flipper::Types::Boolean.new)
219
207
  expect(result).to eq(adapter_result)
220
208
  end
221
209
  end
@@ -230,9 +218,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
230
218
  it 'unmemoizes feature' do
231
219
  feature = flipper[:stats]
232
220
  gate = feature.gate(:boolean)
233
- cache[described_class.key_for(feature.key)] = { some: 'thing' }
234
- subject.disable(feature, gate, flipper.bool)
235
- expect(cache[described_class.key_for(feature.key)]).to be_nil
221
+ cache["feature/#{feature.key}"] = { some: 'thing' }
222
+ subject.disable(feature, gate, Flipper::Types::Boolean.new)
223
+ expect(cache["feature/#{feature.key}"]).to be_nil
236
224
  end
237
225
  end
238
226
 
@@ -244,13 +232,43 @@ RSpec.describe Flipper::Adapters::Memoizable do
244
232
  it 'returns result' do
245
233
  feature = flipper[:stats]
246
234
  gate = feature.gate(:boolean)
247
- result = subject.disable(feature, gate, flipper.bool)
248
- adapter_result = adapter.disable(feature, gate, flipper.bool)
235
+ result = subject.disable(feature, gate, Flipper::Types::Boolean.new)
236
+ adapter_result = adapter.disable(feature, gate, Flipper::Types::Boolean.new)
249
237
  expect(result).to eq(adapter_result)
250
238
  end
251
239
  end
252
240
  end
253
241
 
242
+ describe "#import" do
243
+ context "with memoization enabled" do
244
+ before do
245
+ subject.memoize = true
246
+ end
247
+
248
+ it "unmemoizes features" do
249
+ cache[:foo] = "bar"
250
+ flipper[:stats].enable
251
+ flipper[:search].disable
252
+ subject.import(Flipper::Adapters::Memory.new)
253
+ expect(cache).to be_empty
254
+ end
255
+ end
256
+
257
+ context "with memoization disabled" do
258
+ before do
259
+ subject.memoize = false
260
+ end
261
+
262
+ it "does not unmemoize features" do
263
+ cache[:foo] = "bar"
264
+ flipper[:stats].enable
265
+ flipper[:search].disable
266
+ subject.import(Flipper::Adapters::Memory.new)
267
+ expect(cache).not_to be_empty
268
+ end
269
+ end
270
+ end
271
+
254
272
  describe '#features' do
255
273
  context 'with memoization enabled' do
256
274
  before do
@@ -314,9 +332,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
314
332
 
315
333
  it 'unmemoizes the feature' do
316
334
  feature = flipper[:stats]
317
- cache[described_class.key_for(feature.key)] = { some: 'thing' }
335
+ cache["feature/#{feature.key}"] = { some: 'thing' }
318
336
  subject.remove(feature)
319
- expect(cache[described_class.key_for(feature.key)]).to be_nil
337
+ expect(cache["feature/#{feature.key}"]).to be_nil
320
338
  end
321
339
  end
322
340
 
@@ -339,9 +357,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
339
357
 
340
358
  it 'unmemoizes feature' do
341
359
  feature = flipper[:stats]
342
- cache[described_class.key_for(feature.key)] = { some: 'thing' }
360
+ cache["feature/#{feature.key}"] = { some: 'thing' }
343
361
  subject.clear(feature)
344
- expect(cache[described_class.key_for(feature.key)]).to be_nil
362
+ expect(cache["feature/#{feature.key}"]).to be_nil
345
363
  end
346
364
  end
347
365
 
@@ -1,8 +1,36 @@
1
- require 'helper'
2
- require 'flipper/spec/shared_adapter_specs'
3
-
4
1
  RSpec.describe Flipper::Adapters::Memory do
5
- subject { described_class.new }
2
+ let(:source) { {} }
3
+
4
+ context 'threadsafe: true' do
5
+ subject { described_class.new(source, threadsafe: true) }
6
+
7
+ it_should_behave_like 'a flipper adapter'
8
+ end
9
+
10
+ context 'threadsafe: false' do
11
+ subject { described_class.new(source, threadsafe: false) }
12
+
13
+ it_should_behave_like 'a flipper adapter'
14
+ end
15
+
16
+ it "can initialize from big hash" do
17
+ flipper = Flipper.new(subject)
18
+ flipper.enable :subscriptions
19
+ flipper.disable :search
20
+ flipper.enable_percentage_of_actors :pro_deal, 20
21
+ flipper.enable_percentage_of_time :logging, 30
22
+ flipper.enable_actor :following, Flipper::Actor.new('1')
23
+ flipper.enable_actor :following, Flipper::Actor.new('3')
24
+ flipper.enable_group :following, Flipper::Types::Group.new(:staff)
25
+
26
+ dup = described_class.new(subject.get_all)
6
27
 
7
- it_should_behave_like 'a flipper adapter'
28
+ expect(dup.get_all).to eq({
29
+ "subscriptions" => subject.default_config.merge(boolean: "true"),
30
+ "search" => subject.default_config,
31
+ "logging" => subject.default_config.merge(:percentage_of_time => "30"),
32
+ "pro_deal" => subject.default_config.merge(:percentage_of_actors => "20"),
33
+ "following" => subject.default_config.merge(actors: Set["1", "3"], groups: Set["staff"]),
34
+ })
35
+ end
8
36
  end
@@ -1,6 +1,4 @@
1
- require 'helper'
2
1
  require 'flipper/adapters/operation_logger'
3
- require 'flipper/spec/shared_adapter_specs'
4
2
 
5
3
  RSpec.describe Flipper::Adapters::OperationLogger do
6
4
  let(:operations) { [] }
@@ -11,14 +9,13 @@ RSpec.describe Flipper::Adapters::OperationLogger do
11
9
 
12
10
  it_should_behave_like 'a flipper adapter'
13
11
 
14
- it 'forwards missing methods to underlying adapter' do
15
- adapter = Class.new do
16
- def foo
17
- :foo
18
- end
19
- end.new
20
- operation_logger = described_class.new(adapter)
21
- expect(operation_logger.foo).to eq(:foo)
12
+ it 'shows itself when inspect' do
13
+ subject.features
14
+ output = subject.inspect
15
+ expect(output).to match(/OperationLogger/)
16
+ expect(output).to match(/operation_logger/)
17
+ expect(output).to match(/@type=:features/)
18
+ expect(output).to match(/@adapter=#<Flipper::Adapters::Memory/)
22
19
  end
23
20
 
24
21
  describe '#get' do
@@ -40,7 +37,7 @@ RSpec.describe Flipper::Adapters::OperationLogger do
40
37
  before do
41
38
  @feature = flipper[:stats]
42
39
  @gate = @feature.gate(:boolean)
43
- @thing = flipper.bool
40
+ @thing = Flipper::Types::Boolean.new
44
41
  @result = subject.enable(@feature, @gate, @thing)
45
42
  end
46
43
 
@@ -57,7 +54,7 @@ RSpec.describe Flipper::Adapters::OperationLogger do
57
54
  before do
58
55
  @feature = flipper[:stats]
59
56
  @gate = @feature.gate(:boolean)
60
- @thing = flipper.bool
57
+ @thing = Flipper::Types::Boolean.new
61
58
  @result = subject.disable(@feature, @gate, @thing)
62
59
  end
63
60
 
@@ -99,4 +96,33 @@ RSpec.describe Flipper::Adapters::OperationLogger do
99
96
  expect(@result).to eq(adapter.add(@feature))
100
97
  end
101
98
  end
99
+
100
+ describe '#import' do
101
+ before do
102
+ @source = Flipper::Adapters::Memory.new
103
+ @result = subject.import(@source)
104
+ end
105
+
106
+ it 'logs operation' do
107
+ expect(subject.count(:import)).to be(1)
108
+ end
109
+
110
+ it 'returns result' do
111
+ expect(@result).to eq(adapter.import(@source))
112
+ end
113
+ end
114
+
115
+ describe '#export' do
116
+ before do
117
+ @result = subject.export(format: :json, version: 1)
118
+ end
119
+
120
+ it 'logs operation' do
121
+ expect(subject.count(:export)).to be(1)
122
+ end
123
+
124
+ it 'returns result' do
125
+ expect(@result).to eq(adapter.export(format: :json, version: 1))
126
+ end
127
+ end
102
128
  end
@@ -0,0 +1,41 @@
1
+ require 'flipper/adapters/poll'
2
+
3
+ RSpec.describe Flipper::Adapters::Poll do
4
+ let(:remote_adapter) {
5
+ adapter = Flipper::Adapters::Memory.new(threadsafe: true)
6
+ flipper = Flipper.new(adapter)
7
+ flipper.enable(:search)
8
+ flipper.enable(:analytics)
9
+ adapter
10
+ }
11
+ let(:local_adapter) { Flipper::Adapters::Memory.new(threadsafe: true) }
12
+ let(:poller) {
13
+ Flipper::Poller.get("for_spec", {
14
+ start_automatically: false,
15
+ remote_adapter: remote_adapter,
16
+ })
17
+ }
18
+
19
+ it "syncs in main thread if local adapter is empty" do
20
+ instance = described_class.new(poller, local_adapter)
21
+ instance.features # call something to force sync
22
+ expect(local_adapter.features).to eq(remote_adapter.features)
23
+ end
24
+
25
+ it "does not sync in main thread if local adapter is not empty" do
26
+ # make local not empty by importing remote
27
+ flipper = Flipper.new(local_adapter)
28
+ flipper.import(remote_adapter)
29
+
30
+ # make a fake poller to verify calls
31
+ poller = double("Poller", last_synced_at: Concurrent::AtomicFixnum.new(0))
32
+ expect(poller).to receive(:start).twice
33
+ expect(poller).not_to receive(:sync)
34
+
35
+ # create new instance and call something to force sync
36
+ instance = described_class.new(poller, local_adapter)
37
+ instance.features # call something to force sync
38
+
39
+ expect(local_adapter.features).to eq(remote_adapter.features)
40
+ end
41
+ end
@@ -1,6 +1,4 @@
1
- require 'helper'
2
1
  require 'flipper/adapters/pstore'
3
- require 'flipper/spec/shared_adapter_specs'
4
2
 
5
3
  RSpec.describe Flipper::Adapters::PStore do
6
4
  subject do
@@ -1,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/adapters/read_only'
3
2
 
4
3
  RSpec.describe Flipper::Adapters::ReadOnly do
@@ -6,11 +5,12 @@ RSpec.describe Flipper::Adapters::ReadOnly do
6
5
  let(:flipper) { Flipper.new(subject) }
7
6
  let(:feature) { flipper[:stats] }
8
7
 
9
- let(:boolean_gate) { feature.gate(:boolean) }
10
- let(:group_gate) { feature.gate(:group) }
11
- let(:actor_gate) { feature.gate(:actor) }
12
- let(:actors_gate) { feature.gate(:percentage_of_actors) }
13
- let(:time_gate) { feature.gate(:percentage_of_time) }
8
+ let(:boolean_gate) { feature.gate(:boolean) }
9
+ let(:group_gate) { feature.gate(:group) }
10
+ let(:actor_gate) { feature.gate(:actor) }
11
+ let(:expression_gate) { feature.gate(:expression) }
12
+ let(:actors_gate) { feature.gate(:percentage_of_actors) }
13
+ let(:time_gate) { feature.gate(:percentage_of_time) }
14
14
 
15
15
  subject { described_class.new(adapter) }
16
16
 
@@ -42,18 +42,28 @@ RSpec.describe Flipper::Adapters::ReadOnly do
42
42
  end
43
43
 
44
44
  it 'can get feature' do
45
+ expression = Flipper.property(:plan).eq("basic")
45
46
  actor22 = Flipper::Actor.new('22')
46
- adapter.enable(feature, boolean_gate, flipper.boolean)
47
+ adapter.enable(feature, boolean_gate, Flipper::Types::Boolean.new)
47
48
  adapter.enable(feature, group_gate, flipper.group(:admins))
48
- adapter.enable(feature, actor_gate, flipper.actor(actor22))
49
- adapter.enable(feature, actors_gate, flipper.actors(25))
50
- adapter.enable(feature, time_gate, flipper.time(45))
51
-
52
- expect(subject.get(feature)).to eq(boolean: 'true',
53
- groups: Set['admins'],
54
- actors: Set['22'],
55
- percentage_of_actors: '25',
56
- percentage_of_time: '45')
49
+ adapter.enable(feature, actor_gate, Flipper::Types::Actor.new(actor22))
50
+ adapter.enable(feature, actors_gate, Flipper::Types::PercentageOfActors.new(25))
51
+ adapter.enable(feature, time_gate, Flipper::Types::PercentageOfTime.new(45))
52
+ adapter.enable(feature, expression_gate, expression)
53
+
54
+ expect(subject.get(feature)).to eq({
55
+ boolean: 'true',
56
+ groups: Set['admins'],
57
+ actors: Set['22'],
58
+ expression: {
59
+ "Equal" => [
60
+ {"Property" => ["plan"]},
61
+ "basic",
62
+ ]
63
+ },
64
+ percentage_of_actors: '25',
65
+ percentage_of_time: '45',
66
+ })
57
67
  end
58
68
 
59
69
  it 'can get features' do
@@ -62,6 +72,10 @@ RSpec.describe Flipper::Adapters::ReadOnly do
62
72
  expect(subject.features).to eq(Set['stats'])
63
73
  end
64
74
 
75
+ it 'is configured as read only' do
76
+ expect(subject.read_only?).to eq(true)
77
+ end
78
+
65
79
  it 'raises error on add' do
66
80
  expect { subject.add(feature) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
67
81
  end
@@ -75,12 +89,12 @@ RSpec.describe Flipper::Adapters::ReadOnly do
75
89
  end
76
90
 
77
91
  it 'raises error on enable' do
78
- expect { subject.enable(feature, boolean_gate, flipper.boolean) }
92
+ expect { subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new) }
79
93
  .to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
80
94
  end
81
95
 
82
96
  it 'raises error on disable' do
83
- expect { subject.disable(feature, boolean_gate, flipper.boolean) }
97
+ expect { subject.disable(feature, boolean_gate, Flipper::Types::Boolean.new) }
84
98
  .to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
85
99
  end
86
100
  end
@@ -0,0 +1,64 @@
1
+ RSpec.describe Flipper::Adapters::Strict do
2
+ let(:flipper) { Flipper.new(subject) }
3
+ let(:feature) { flipper[:unknown] }
4
+
5
+ it_should_behave_like 'a flipper adapter' do
6
+ subject { described_class.new(Flipper::Adapters::Memory.new, :noop) }
7
+ end
8
+
9
+ [true, :raise].each do |handler|
10
+ context "handler = #{handler}" do
11
+ subject { described_class.new(Flipper::Adapters::Memory.new, handler) }
12
+
13
+ context "#get" do
14
+ it "raises an error for unknown feature" do
15
+ expect { subject.get(feature) }.to raise_error(Flipper::Adapters::Strict::NotFound)
16
+ end
17
+ end
18
+
19
+ context "#get_multi" do
20
+ it "raises an error for unknown feature" do
21
+ expect { subject.get_multi([feature]) }.to raise_error(Flipper::Adapters::Strict::NotFound)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ context "handler = :warn" do
28
+ subject { described_class.new(Flipper::Adapters::Memory.new, :warn) }
29
+
30
+ context "#get" do
31
+ it "raises an error for unknown feature" do
32
+ expect(capture_output { subject.get(feature) }).to match(/Could not find feature "unknown"/)
33
+ end
34
+ end
35
+
36
+ context "#get_multi" do
37
+ it "raises an error for unknown feature" do
38
+ expect(capture_output { subject.get_multi([feature]) }).to match(/Could not find feature "unknown"/)
39
+ end
40
+ end
41
+ end
42
+
43
+ context "handler = Block" do
44
+ let(:unknown_features) { [] }
45
+ subject do
46
+ described_class.new(Flipper::Adapters::Memory.new) { |feature| unknown_features << feature.key}
47
+ end
48
+
49
+
50
+ context "#get" do
51
+ it "raises an error for unknown feature" do
52
+ subject.get(feature)
53
+ expect(unknown_features).to eq(["unknown"])
54
+ end
55
+ end
56
+
57
+ context "#get_multi" do
58
+ it "raises an error for unknown feature" do
59
+ subject.get_multi([flipper[:foo], flipper[:bar]])
60
+ expect(unknown_features).to eq(["foo", "bar"])
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,4 +1,3 @@
1
- require "helper"
2
1
  require "flipper/adapters/memory"
3
2
  require "flipper/adapters/operation_logger"
4
3
  require "flipper/adapters/sync/feature_synchronizer"
@@ -9,6 +8,8 @@ RSpec.describe Flipper::Adapters::Sync::FeatureSynchronizer do
9
8
  end
10
9
  let(:flipper) { Flipper.new(adapter) }
11
10
  let(:feature) { flipper[:search] }
11
+ let(:plan_expression) { Flipper.property(:plan).eq("basic") }
12
+ let(:age_expression) { Flipper.property(:age).gte(21) }
12
13
 
13
14
  context "when remote disabled" do
14
15
  let(:remote) { Flipper::GateValues.new({}) }
@@ -64,6 +65,7 @@ RSpec.describe Flipper::Adapters::Sync::FeatureSynchronizer do
64
65
  boolean: nil,
65
66
  actors: Set["1"],
66
67
  groups: Set["staff"],
68
+ expression: plan_expression.value,
67
69
  percentage_of_time: 10,
68
70
  percentage_of_actors: 15,
69
71
  }
@@ -75,10 +77,46 @@ RSpec.describe Flipper::Adapters::Sync::FeatureSynchronizer do
75
77
  expect(local_gate_values_hash.fetch(:boolean)).to be(nil)
76
78
  expect(local_gate_values_hash.fetch(:actors)).to eq(Set["1"])
77
79
  expect(local_gate_values_hash.fetch(:groups)).to eq(Set["staff"])
80
+ expect(local_gate_values_hash.fetch(:expression)).to eq(plan_expression.value)
78
81
  expect(local_gate_values_hash.fetch(:percentage_of_time)).to eq("10")
79
82
  expect(local_gate_values_hash.fetch(:percentage_of_actors)).to eq("15")
80
83
  end
81
84
 
85
+ it "updates expression when remote is updated" do
86
+ any_expression = Flipper.any(plan_expression, age_expression)
87
+ remote = Flipper::GateValues.new(expression: any_expression.value)
88
+ feature.enable_expression(age_expression)
89
+ adapter.reset
90
+
91
+ described_class.new(feature, feature.gate_values, remote).call
92
+
93
+ expect(feature.expression_value).to eq(any_expression.value)
94
+ expect_only_enable
95
+ end
96
+
97
+ it "does nothing to expression if in sync" do
98
+ remote = Flipper::GateValues.new(expression: plan_expression.value)
99
+ feature.enable_expression(plan_expression)
100
+ adapter.reset
101
+
102
+ described_class.new(feature, feature.gate_values, remote).call
103
+
104
+ expect(feature.expression_value).to eq(plan_expression.value)
105
+ expect_no_enable_or_disable
106
+ end
107
+
108
+ it "updates expression when remote conditionally enabled but expression is nil" do
109
+ remote = Flipper::GateValues.new(expression: nil, actors: Set["1"])
110
+ feature.enable_expression(plan_expression)
111
+ feature.enable_actor(Flipper::Actor.new("1"))
112
+ adapter.reset
113
+
114
+ described_class.new(feature, feature.gate_values, remote).call
115
+
116
+ expect(feature.expression_value).to eq(nil)
117
+ expect_only_disable
118
+ end
119
+
82
120
  it "adds remotely added actors" do
83
121
  remote = Flipper::GateValues.new(actors: Set["1", "2"])
84
122
  feature.enable_actor(Flipper::Actor.new("1"))
@@ -1,10 +1,10 @@
1
- require "helper"
2
1
  require "flipper/adapters/sync/interval_synchronizer"
3
2
 
4
3
  RSpec.describe Flipper::Adapters::Sync::IntervalSynchronizer do
5
4
  let(:events) { [] }
6
- let(:synchronizer) { -> { events << described_class.now } }
5
+ let(:synchronizer) { -> { events << now } }
7
6
  let(:interval) { 10 }
7
+ let(:now) { subject.send(:now) }
8
8
 
9
9
  subject { described_class.new(synchronizer, interval: interval) }
10
10
 
@@ -15,19 +15,18 @@ RSpec.describe Flipper::Adapters::Sync::IntervalSynchronizer do
15
15
  end
16
16
 
17
17
  it "only invokes wrapped synchronizer every interval seconds" do
18
- now = described_class.now
19
18
  subject.call
20
19
  events.clear
21
20
 
22
21
  # move time to one millisecond less than last sync + interval
23
22
  1.upto(interval) do |i|
24
- allow(described_class).to receive(:now).and_return(now + i - 1)
23
+ allow(subject).to receive(:now).and_return(now + i - 1)
25
24
  subject.call
26
25
  end
27
26
  expect(events.size).to be(0)
28
27
 
29
28
  # move time to last sync + interval in milliseconds
30
- allow(described_class).to receive(:now).and_return(now + interval)
29
+ allow(subject).to receive(:now).and_return(now + interval)
31
30
  subject.call
32
31
  expect(events.size).to be(1)
33
32
  end