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,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/feature'
3
2
  require 'flipper/instrumenters/memory'
4
3
 
@@ -33,6 +32,72 @@ RSpec.describe Flipper::Feature do
33
32
  end
34
33
  end
35
34
 
35
+ describe "#enabled?" do
36
+ context "for an actor" do
37
+ let(:actor) { Flipper::Actor.new("User;1") }
38
+
39
+ it 'returns true if feature is enabled' do
40
+ subject.enable
41
+ expect(subject.enabled?(actor)).to be(true)
42
+ end
43
+
44
+ it 'returns false if feature is disabled' do
45
+ subject.disable
46
+ expect(subject.enabled?(actor)).to be(false)
47
+ end
48
+ end
49
+
50
+ context "for multiple actors" do
51
+ let(:actors) {
52
+ [
53
+ Flipper::Actor.new("User;1"),
54
+ Flipper::Actor.new("User;2"),
55
+ Flipper::Actor.new("User;3"),
56
+ ]
57
+ }
58
+
59
+ it 'returns true if feature is enabled' do
60
+ subject.enable
61
+ expect(subject.enabled?(actors)).to be(true)
62
+ end
63
+
64
+ it 'returns true if feature is enabled for any actor' do
65
+ subject.enable_actor actors.first
66
+ expect(subject.enabled?(actors)).to be(true)
67
+ end
68
+
69
+ it 'returns true if feature is enabled for any actor with multiple arguments' do
70
+ subject.enable_actor actors.last
71
+ expect(subject.enabled?(*actors)).to be(true)
72
+ end
73
+
74
+ it 'returns false if feature is disabled for all actors' do
75
+ subject.disable
76
+ expect(subject.enabled?(actors)).to be(false)
77
+ end
78
+ end
79
+
80
+ context "for an object that implements .nil? == true" do
81
+ let(:actor) { Flipper::Actor.new("User;1") }
82
+
83
+ before do
84
+ def actor.nil?
85
+ true
86
+ end
87
+ end
88
+
89
+ it 'returns true if feature is enabled' do
90
+ subject.enable
91
+ expect(subject.enabled?(actor)).to be(true)
92
+ end
93
+
94
+ it 'returns false if feature is disabled' do
95
+ subject.disable
96
+ expect(subject.enabled?(actor)).to be(false)
97
+ end
98
+ end
99
+ end
100
+
36
101
  describe '#to_s' do
37
102
  it 'returns name as string' do
38
103
  feature = described_class.new(:search, adapter)
@@ -64,7 +129,7 @@ RSpec.describe Flipper::Feature do
64
129
  instance.gates.each do |gate|
65
130
  expect(gate).to be_a(Flipper::Gate)
66
131
  end
67
- expect(instance.gates.size).to be(5)
132
+ expect(instance.gates.size).to be(6)
68
133
  end
69
134
  end
70
135
 
@@ -149,34 +214,33 @@ RSpec.describe Flipper::Feature do
149
214
  end
150
215
 
151
216
  it 'is recorded for enable' do
152
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
153
- gate = subject.gate_for(thing)
217
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
218
+ subject.gate_for(actor)
154
219
 
155
- subject.enable(thing)
220
+ subject.enable(actor)
156
221
 
157
222
  event = instrumenter.events.last
158
223
  expect(event).not_to be_nil
159
224
  expect(event.name).to eq('feature_operation.flipper')
160
225
  expect(event.payload[:feature_name]).to eq(:search)
161
226
  expect(event.payload[:operation]).to eq(:enable)
162
- expect(event.payload[:thing]).to eq(thing)
227
+ expect(event.payload[:thing]).to eq(actor)
163
228
  expect(event.payload[:result]).not_to be_nil
164
229
  end
165
230
 
166
231
  it 'always instruments flipper type instance for enable' do
167
- thing = Flipper::Actor.new('1')
168
- gate = subject.gate_for(thing)
232
+ actor = Flipper::Actor.new('1')
233
+ subject.gate_for(actor)
169
234
 
170
- subject.enable(thing)
235
+ subject.enable(actor)
171
236
 
172
237
  event = instrumenter.events.last
173
238
  expect(event).not_to be_nil
174
- expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
239
+ expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
175
240
  end
176
241
 
177
242
  it 'is recorded for disable' do
178
243
  thing = Flipper::Types::Boolean.new
179
- gate = subject.gate_for(thing)
180
244
 
181
245
  subject.disable(thing)
182
246
 
@@ -220,15 +284,14 @@ RSpec.describe Flipper::Feature do
220
284
  end
221
285
 
222
286
  it 'always instruments flipper type instance for disable' do
223
- thing = Flipper::Actor.new('1')
224
- gate = subject.gate_for(thing)
287
+ actor = Flipper::Actor.new('1')
225
288
 
226
- subject.disable(thing)
289
+ subject.disable(actor)
227
290
 
228
291
  event = instrumenter.events.last
229
292
  expect(event).not_to be_nil
230
293
  expect(event.payload[:operation]).to eq(:disable)
231
- expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
294
+ expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
232
295
  end
233
296
 
234
297
  it 'is recorded for add' do
@@ -276,17 +339,15 @@ RSpec.describe Flipper::Feature do
276
339
  end
277
340
 
278
341
  it 'is recorded for enabled?' do
279
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
280
- gate = subject.gate_for(thing)
281
-
282
- subject.enabled?(thing)
342
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
343
+ subject.enabled?(actor)
283
344
 
284
345
  event = instrumenter.events.last
285
346
  expect(event).not_to be_nil
286
347
  expect(event.name).to eq('feature_operation.flipper')
287
348
  expect(event.payload[:feature_name]).to eq(:search)
288
349
  expect(event.payload[:operation]).to eq(:enabled?)
289
- expect(event.payload[:thing]).to eq(thing)
350
+ expect(event.payload[:actors]).to eq([actor])
290
351
  expect(event.payload[:result]).to eq(false)
291
352
  end
292
353
 
@@ -294,8 +355,8 @@ RSpec.describe Flipper::Feature do
294
355
  actor = Flipper::Types::Actor.new(user)
295
356
  {
296
357
  nil => nil,
297
- user => actor,
298
- actor => actor,
358
+ user => [actor],
359
+ actor => [actor],
299
360
  }.each do |thing, wrapped_thing|
300
361
  it "always instruments #{thing.inspect} as #{wrapped_thing.class} for enabled?" do
301
362
  subject.enabled?(thing)
@@ -303,7 +364,7 @@ RSpec.describe Flipper::Feature do
303
364
  event = instrumenter.events.last
304
365
  expect(event).not_to be_nil
305
366
  expect(event.payload[:operation]).to eq(:enabled?)
306
- expect(event.payload[:thing]).to eq(wrapped_thing)
367
+ expect(event.payload[:actors]).to eq(wrapped_thing)
307
368
  end
308
369
  end
309
370
  end
@@ -359,19 +420,19 @@ RSpec.describe Flipper::Feature do
359
420
  end
360
421
 
361
422
  it 'returns :on' do
362
- expect(subject.state).to be(:on)
423
+ expect(subject.state).to be(:conditional)
363
424
  end
364
425
 
365
- it 'returns true for on?' do
366
- expect(subject.on?).to be(true)
426
+ it 'returns false for on?' do
427
+ expect(subject.on?).to be(false)
367
428
  end
368
429
 
369
430
  it 'returns false for off?' do
370
431
  expect(subject.off?).to be(false)
371
432
  end
372
433
 
373
- it 'returns false for conditional?' do
374
- expect(subject.conditional?).to be(false)
434
+ it 'returns true for conditional?' do
435
+ expect(subject.conditional?).to be(true)
375
436
  end
376
437
  end
377
438
 
@@ -429,10 +490,10 @@ RSpec.describe Flipper::Feature do
429
490
 
430
491
  context 'when one or more groups enabled' do
431
492
  before do
432
- @staff = Flipper.register(:staff) { |_thing| true }
433
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
434
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
435
- @disabled = Flipper.register(:disabled) { |_thing| true }
493
+ @staff = Flipper.register(:staff) { |actor| true }
494
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
495
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
496
+ @disabled = Flipper.register(:disabled) { |actor| true }
436
497
  subject.enable @staff
437
498
  subject.enable @preview_features
438
499
  subject.disable @disabled
@@ -468,10 +529,10 @@ RSpec.describe Flipper::Feature do
468
529
 
469
530
  context 'when one or more groups enabled' do
470
531
  before do
471
- @staff = Flipper.register(:staff) { |_thing| true }
472
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
473
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
474
- @disabled = Flipper.register(:disabled) { |_thing| true }
532
+ @staff = Flipper.register(:staff) { |actor| true }
533
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
534
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
535
+ @disabled = Flipper.register(:disabled) { |actor| true }
475
536
  subject.enable @staff
476
537
  subject.enable @preview_features
477
538
  subject.disable @disabled
@@ -495,10 +556,10 @@ RSpec.describe Flipper::Feature do
495
556
 
496
557
  context 'when one or more groups enabled' do
497
558
  before do
498
- @staff = Flipper.register(:staff) { |_thing| true }
499
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
500
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
501
- @disabled = Flipper.register(:disabled) { |_thing| true }
559
+ @staff = Flipper.register(:staff) { |actor| true }
560
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
561
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
562
+ @disabled = Flipper.register(:disabled) { |actor| true }
502
563
  subject.enable @staff
503
564
  subject.enable @preview_features
504
565
  subject.disable @disabled
@@ -530,12 +591,12 @@ RSpec.describe Flipper::Feature do
530
591
 
531
592
  context 'when one or more actors are enabled' do
532
593
  before do
533
- subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User:5'))
534
- subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User:22'))
594
+ subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User;5'))
595
+ subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User;22'))
535
596
  end
536
597
 
537
598
  it 'returns set of actor ids' do
538
- expect(subject.actors_value).to eq(Set.new(['User:5', 'User:22']))
599
+ expect(subject.actors_value).to eq(Set.new(['User;5', 'User;22']))
539
600
  end
540
601
  end
541
602
  end
@@ -650,6 +711,361 @@ RSpec.describe Flipper::Feature do
650
711
  end
651
712
  end
652
713
 
714
+ describe '#expression' do
715
+ it "returns nil if feature has no expression" do
716
+ expect(subject.expression).to be(nil)
717
+ end
718
+
719
+ it "returns expression if feature has expression" do
720
+ expression = Flipper.property(:plan).eq("basic")
721
+ subject.enable_expression expression
722
+ expect(subject.expression).to eq(expression)
723
+ end
724
+ end
725
+
726
+ describe '#enable_expression/disable_expression' do
727
+ context "with expression instance" do
728
+ it "updates gate values to equal expression or clears expression" do
729
+ expression = Flipper.property(:plan).eq("basic")
730
+ expect(subject.gate_values.expression).to be(nil)
731
+ subject.enable_expression(expression)
732
+ expect(subject.gate_values.expression).to eq(expression.value)
733
+ subject.disable_expression
734
+ expect(subject.gate_values.expression).to be(nil)
735
+ end
736
+ end
737
+
738
+ context "with Hash" do
739
+ it "updates gate values to equal expression or clears expression" do
740
+ expression = Flipper.property(:plan).eq("basic")
741
+ expect(subject.gate_values.expression).to be(nil)
742
+ subject.enable_expression(expression.value)
743
+ expect(subject.gate_values.expression).to eq(expression.value)
744
+ subject.disable_expression
745
+ expect(subject.gate_values.expression).to be(nil)
746
+ end
747
+ end
748
+ end
749
+
750
+ describe "#add_expression" do
751
+ context "when nothing enabled" do
752
+ context "with Expression instance" do
753
+ it "sets expression to Expression" do
754
+ expression = Flipper.property(:plan).eq("basic")
755
+ subject.add_expression(expression)
756
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
757
+ expect(subject.expression).to eq(expression)
758
+ end
759
+ end
760
+
761
+ context "with Any instance" do
762
+ it "sets expression to Any" do
763
+ expression = Flipper.any(Flipper.property(:plan).eq("basic"))
764
+ subject.add_expression(expression)
765
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
766
+ expect(subject.expression).to eq(expression)
767
+ end
768
+ end
769
+
770
+ context "with All instance" do
771
+ it "sets expression to All" do
772
+ expression = Flipper.all(Flipper.property(:plan).eq("basic"))
773
+ subject.add_expression(expression)
774
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
775
+ expect(subject.expression).to eq(expression)
776
+ end
777
+ end
778
+ end
779
+
780
+ context "when Expression enabled" do
781
+ let(:expression) { Flipper.property(:plan).eq("basic") }
782
+
783
+ before do
784
+ subject.enable_expression expression
785
+ end
786
+
787
+ context "with Expression instance" do
788
+ it "changes expression to Any and adds new Expression" do
789
+ new_expression = Flipper.property(:age).gte(21)
790
+ subject.add_expression(new_expression)
791
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
792
+ expect(subject.expression.args).to include(expression)
793
+ expect(subject.expression.args).to include(new_expression)
794
+ end
795
+ end
796
+
797
+ context "with Any instance" do
798
+ it "changes expression to Any and adds new Any" do
799
+ new_expression = Flipper.any(Flipper.property(:age).eq(21))
800
+ subject.add_expression new_expression
801
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
802
+ expect(subject.expression.args).to include(expression)
803
+ expect(subject.expression.args).to include(new_expression)
804
+ end
805
+ end
806
+
807
+ context "with All instance" do
808
+ it "changes expression to Any and adds new All" do
809
+ new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
810
+ subject.add_expression new_expression
811
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
812
+ expect(subject.expression.args).to include(expression)
813
+ expect(subject.expression.args).to include(new_expression)
814
+ end
815
+ end
816
+ end
817
+
818
+ context "when Any enabled" do
819
+ let(:condition) { Flipper.property(:plan).eq("basic") }
820
+ let(:expression) { Flipper.any(condition) }
821
+
822
+ before do
823
+ subject.enable_expression expression
824
+ end
825
+
826
+ context "with Expression instance" do
827
+ it "adds Expression to Any" do
828
+ new_expression = Flipper.property(:age).gte(21)
829
+ subject.add_expression(new_expression)
830
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
831
+ expect(subject.expression.args).to include(condition)
832
+ expect(subject.expression.args).to include(new_expression)
833
+ end
834
+ end
835
+
836
+ context "with Any instance" do
837
+ it "adds Any to Any" do
838
+ new_expression = Flipper.any(Flipper.property(:age).gte(21))
839
+ subject.add_expression(new_expression)
840
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
841
+ expect(subject.expression.args).to include(condition)
842
+ expect(subject.expression.args).to include(new_expression)
843
+ end
844
+ end
845
+
846
+ context "with All instance" do
847
+ it "adds All to Any" do
848
+ new_expression = Flipper.all(Flipper.property(:age).gte(21))
849
+ subject.add_expression(new_expression)
850
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
851
+ expect(subject.expression.args).to include(condition)
852
+ expect(subject.expression.args).to include(new_expression)
853
+ end
854
+ end
855
+ end
856
+
857
+ context "when All enabled" do
858
+ let(:condition) { Flipper.property(:plan).eq("basic") }
859
+ let(:expression) { Flipper.all(condition) }
860
+
861
+ before do
862
+ subject.enable_expression expression
863
+ end
864
+
865
+ context "with Expression instance" do
866
+ it "adds Expression to All" do
867
+ new_expression = Flipper.property(:age).gte(21)
868
+ subject.add_expression(new_expression)
869
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
870
+ expect(subject.expression.args).to include(condition)
871
+ expect(subject.expression.args).to include(new_expression)
872
+ end
873
+ end
874
+
875
+ context "with Any instance" do
876
+ it "adds Any to All" do
877
+ new_expression = Flipper.any(Flipper.property(:age).gte(21))
878
+ subject.add_expression(new_expression)
879
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
880
+ expect(subject.expression.args).to include(condition)
881
+ expect(subject.expression.args).to include(new_expression)
882
+ end
883
+ end
884
+
885
+ context "with All instance" do
886
+ it "adds All to All" do
887
+ new_expression = Flipper.all(Flipper.property(:age).gte(21))
888
+ subject.add_expression(new_expression)
889
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
890
+ expect(subject.expression.args).to include(condition)
891
+ expect(subject.expression.args).to include(new_expression)
892
+ end
893
+ end
894
+ end
895
+ end
896
+
897
+ describe '#remove_expression' do
898
+ context "when nothing enabled" do
899
+ context "with Expression instance" do
900
+ it "does nothing" do
901
+ expression = Flipper.property(:plan).eq("basic")
902
+ subject.remove_expression(expression)
903
+ expect(subject.expression).to be(nil)
904
+ end
905
+ end
906
+
907
+ context "with Any instance" do
908
+ it "does nothing" do
909
+ expression = Flipper.any(Flipper.property(:plan).eq("basic"))
910
+ subject.remove_expression expression
911
+ expect(subject.expression).to be(nil)
912
+ end
913
+ end
914
+
915
+ context "with All instance" do
916
+ it "does nothing" do
917
+ expression = Flipper.all(Flipper.property(:plan).eq("basic"))
918
+ subject.remove_expression expression
919
+ expect(subject.expression).to be(nil)
920
+ end
921
+ end
922
+ end
923
+
924
+ context "when Expression enabled" do
925
+ let(:expression) { Flipper.property(:plan).eq("basic") }
926
+
927
+ before do
928
+ subject.enable_expression expression
929
+ end
930
+
931
+ context "with Expression instance" do
932
+ it "changes expression to Any and removes Expression if it matches" do
933
+ new_expression = Flipper.property(:plan).eq("basic")
934
+ subject.remove_expression new_expression
935
+ expect(subject.expression).to eq(Flipper.any)
936
+ end
937
+
938
+ it "changes expression to Any if Expression doesn't match" do
939
+ new_expression = Flipper.property(:plan).eq("premium")
940
+ subject.remove_expression new_expression
941
+ expect(subject.expression).to eq(Flipper.any(expression))
942
+ end
943
+ end
944
+
945
+ context "with Any instance" do
946
+ it "changes expression to Any and does nothing" do
947
+ new_expression = Flipper.any(Flipper.property(:plan).eq("basic"))
948
+ subject.remove_expression new_expression
949
+ expect(subject.expression).to eq(Flipper.any(expression))
950
+ end
951
+ end
952
+
953
+ context "with All instance" do
954
+ it "changes expression to Any and does nothing" do
955
+ new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
956
+ subject.remove_expression new_expression
957
+ expect(subject.expression).to eq(Flipper.any(expression))
958
+ end
959
+ end
960
+ end
961
+
962
+ context "when Any enabled" do
963
+ let(:condition) { Flipper.property(:plan).eq("basic") }
964
+ let(:expression) { Flipper.any condition }
965
+
966
+ before do
967
+ subject.enable_expression expression
968
+ end
969
+
970
+ context "with Expression instance" do
971
+ it "removes Expression if it matches" do
972
+ subject.remove_expression condition
973
+ expect(subject.expression).to eq(Flipper.any)
974
+ end
975
+
976
+ it "does nothing if Expression does not match" do
977
+ subject.remove_expression Flipper.property(:plan).eq("premium")
978
+ expect(subject.expression).to eq(expression)
979
+ end
980
+ end
981
+
982
+ context "with Any instance" do
983
+ it "removes Any if it matches" do
984
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
985
+ subject.add_expression new_expression
986
+ expect(subject.expression.args.size).to be(2)
987
+ subject.remove_expression new_expression
988
+ expect(subject.expression).to eq(expression)
989
+ end
990
+
991
+ it "does nothing if Any does not match" do
992
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
993
+ subject.remove_expression new_expression
994
+ expect(subject.expression).to eq(expression)
995
+ end
996
+ end
997
+
998
+ context "with All instance" do
999
+ it "removes All if it matches" do
1000
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1001
+ subject.add_expression new_expression
1002
+ expect(subject.expression.args.size).to be(2)
1003
+ subject.remove_expression new_expression
1004
+ expect(subject.expression).to eq(expression)
1005
+ end
1006
+
1007
+ it "does nothing if All does not match" do
1008
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1009
+ subject.remove_expression new_expression
1010
+ expect(subject.expression).to eq(expression)
1011
+ end
1012
+ end
1013
+ end
1014
+
1015
+ context "when All enabled" do
1016
+ let(:condition) { Flipper.property(:plan).eq("basic") }
1017
+ let(:expression) { Flipper.all condition }
1018
+
1019
+ before do
1020
+ subject.enable_expression expression
1021
+ end
1022
+
1023
+ context "with Expression instance" do
1024
+ it "removes Expression if it matches" do
1025
+ subject.remove_expression condition
1026
+ expect(subject.expression).to eq(Flipper.all)
1027
+ end
1028
+
1029
+ it "does nothing if Expression does not match" do
1030
+ subject.remove_expression Flipper.property(:plan).eq("premium")
1031
+ expect(subject.expression).to eq(expression)
1032
+ end
1033
+ end
1034
+
1035
+ context "with Any instance" do
1036
+ it "removes Any if it matches" do
1037
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
1038
+ subject.add_expression new_expression
1039
+ expect(subject.expression.args.size).to be(2)
1040
+ subject.remove_expression new_expression
1041
+ expect(subject.expression).to eq(expression)
1042
+ end
1043
+
1044
+ it "does nothing if Any does not match" do
1045
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
1046
+ subject.remove_expression new_expression
1047
+ expect(subject.expression).to eq(expression)
1048
+ end
1049
+ end
1050
+
1051
+ context "with All instance" do
1052
+ it "removes All if it matches" do
1053
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1054
+ subject.add_expression new_expression
1055
+ expect(subject.expression.args.size).to be(2)
1056
+ subject.remove_expression new_expression
1057
+ expect(subject.expression).to eq(expression)
1058
+ end
1059
+
1060
+ it "does nothing if All does not match" do
1061
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1062
+ subject.remove_expression new_expression
1063
+ expect(subject.expression).to eq(expression)
1064
+ end
1065
+ end
1066
+ end
1067
+ end
1068
+
653
1069
  describe '#enable_actor/disable_actor' do
654
1070
  context 'with object that responds to flipper_id' do
655
1071
  it 'updates the gate values to include the actor' do
@@ -678,8 +1094,6 @@ RSpec.describe Flipper::Feature do
678
1094
  describe '#enable_group/disable_group' do
679
1095
  context 'with symbol group name' do
680
1096
  it 'updates the gate values to include the group' do
681
- actor = Flipper::Actor.new(5)
682
- group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
683
1097
  expect(subject.gate_values.groups).to be_empty
684
1098
  subject.enable_group(:five_only)
685
1099
  expect(subject.gate_values.groups).to eq(Set['five_only'])
@@ -690,8 +1104,6 @@ RSpec.describe Flipper::Feature do
690
1104
 
691
1105
  context 'with string group name' do
692
1106
  it 'updates the gate values to include the group' do
693
- actor = Flipper::Actor.new(5)
694
- group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
695
1107
  expect(subject.gate_values.groups).to be_empty
696
1108
  subject.enable_group('five_only')
697
1109
  expect(subject.gate_values.groups).to eq(Set['five_only'])
@@ -702,7 +1114,6 @@ RSpec.describe Flipper::Feature do
702
1114
 
703
1115
  context 'with group instance' do
704
1116
  it 'updates the gate values for the group' do
705
- actor = Flipper::Actor.new(5)
706
1117
  group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
707
1118
  expect(subject.gate_values.groups).to be_empty
708
1119
  subject.enable_group(group)
@@ -802,12 +1213,14 @@ RSpec.describe Flipper::Feature do
802
1213
  :actor,
803
1214
  :boolean,
804
1215
  :group,
1216
+ :expression,
805
1217
  ])
806
1218
 
807
1219
  expect(subject.disabled_gate_names.to_set).to eq(Set[
808
1220
  :actor,
809
1221
  :boolean,
810
1222
  :group,
1223
+ :expression,
811
1224
  ])
812
1225
  end
813
1226
  end
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Gate do
4
2
  let(:feature_name) { :stats }
5
3