flipper 0.26.0 → 1.1.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 (199) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +19 -13
  4. data/.github/workflows/examples.yml +32 -15
  5. data/Changelog.md +294 -154
  6. data/Gemfile +15 -10
  7. data/README.md +13 -11
  8. data/benchmark/enabled_ips.rb +10 -0
  9. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  10. data/benchmark/enabled_profile.rb +20 -0
  11. data/benchmark/instrumentation_ips.rb +21 -0
  12. data/benchmark/typecast_ips.rb +27 -0
  13. data/docs/images/flipper_cloud.png +0 -0
  14. data/examples/api/basic.ru +3 -4
  15. data/examples/api/custom_memoized.ru +3 -4
  16. data/examples/api/memoized.ru +3 -4
  17. data/examples/cloud/app.ru +12 -0
  18. data/examples/cloud/backoff_policy.rb +13 -0
  19. data/examples/cloud/basic.rb +22 -0
  20. data/examples/cloud/cloud_setup.rb +20 -0
  21. data/examples/cloud/forked.rb +36 -0
  22. data/examples/cloud/import.rb +17 -0
  23. data/examples/cloud/threaded.rb +33 -0
  24. data/examples/dsl.rb +1 -15
  25. data/examples/enabled_for_actor.rb +4 -2
  26. data/examples/expressions.rb +213 -0
  27. data/examples/mirroring.rb +59 -0
  28. data/examples/strict.rb +18 -0
  29. data/flipper-cloud.gemspec +19 -0
  30. data/flipper.gemspec +3 -5
  31. data/lib/flipper/actor.rb +6 -3
  32. data/lib/flipper/adapter.rb +33 -7
  33. data/lib/flipper/adapter_builder.rb +44 -0
  34. data/lib/flipper/adapters/dual_write.rb +1 -3
  35. data/lib/flipper/adapters/failover.rb +0 -4
  36. data/lib/flipper/adapters/failsafe.rb +0 -4
  37. data/lib/flipper/adapters/http/client.rb +26 -7
  38. data/lib/flipper/adapters/http/error.rb +1 -1
  39. data/lib/flipper/adapters/http.rb +29 -16
  40. data/lib/flipper/adapters/instrumented.rb +25 -6
  41. data/lib/flipper/adapters/memoizable.rb +33 -21
  42. data/lib/flipper/adapters/memory.rb +81 -46
  43. data/lib/flipper/adapters/operation_logger.rb +16 -7
  44. data/lib/flipper/adapters/poll/poller.rb +2 -125
  45. data/lib/flipper/adapters/poll.rb +5 -3
  46. data/lib/flipper/adapters/pstore.rb +17 -11
  47. data/lib/flipper/adapters/read_only.rb +4 -4
  48. data/lib/flipper/adapters/strict.rb +47 -0
  49. data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
  50. data/lib/flipper/adapters/sync.rb +0 -4
  51. data/lib/flipper/cloud/configuration.rb +258 -0
  52. data/lib/flipper/cloud/dsl.rb +27 -0
  53. data/lib/flipper/cloud/message_verifier.rb +95 -0
  54. data/lib/flipper/cloud/middleware.rb +63 -0
  55. data/lib/flipper/cloud/routes.rb +14 -0
  56. data/lib/flipper/cloud/telemetry/backoff_policy.rb +93 -0
  57. data/lib/flipper/cloud/telemetry/instrumenter.rb +26 -0
  58. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  59. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  60. data/lib/flipper/cloud/telemetry/submitter.rb +98 -0
  61. data/lib/flipper/cloud/telemetry.rb +183 -0
  62. data/lib/flipper/cloud.rb +53 -0
  63. data/lib/flipper/configuration.rb +25 -4
  64. data/lib/flipper/dsl.rb +46 -45
  65. data/lib/flipper/engine.rb +88 -0
  66. data/lib/flipper/errors.rb +3 -3
  67. data/lib/flipper/export.rb +26 -0
  68. data/lib/flipper/exporter.rb +17 -0
  69. data/lib/flipper/exporters/json/export.rb +32 -0
  70. data/lib/flipper/exporters/json/v1.rb +33 -0
  71. data/lib/flipper/expression/builder.rb +73 -0
  72. data/lib/flipper/expression/constant.rb +25 -0
  73. data/lib/flipper/expression.rb +71 -0
  74. data/lib/flipper/expressions/all.rb +11 -0
  75. data/lib/flipper/expressions/any.rb +9 -0
  76. data/lib/flipper/expressions/boolean.rb +9 -0
  77. data/lib/flipper/expressions/comparable.rb +13 -0
  78. data/lib/flipper/expressions/duration.rb +28 -0
  79. data/lib/flipper/expressions/equal.rb +9 -0
  80. data/lib/flipper/expressions/greater_than.rb +9 -0
  81. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  82. data/lib/flipper/expressions/less_than.rb +9 -0
  83. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  84. data/lib/flipper/expressions/not_equal.rb +9 -0
  85. data/lib/flipper/expressions/now.rb +9 -0
  86. data/lib/flipper/expressions/number.rb +9 -0
  87. data/lib/flipper/expressions/percentage.rb +9 -0
  88. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  89. data/lib/flipper/expressions/property.rb +9 -0
  90. data/lib/flipper/expressions/random.rb +9 -0
  91. data/lib/flipper/expressions/string.rb +9 -0
  92. data/lib/flipper/expressions/time.rb +9 -0
  93. data/lib/flipper/feature.rb +87 -26
  94. data/lib/flipper/feature_check_context.rb +10 -6
  95. data/lib/flipper/gate.rb +13 -11
  96. data/lib/flipper/gate_values.rb +5 -18
  97. data/lib/flipper/gates/actor.rb +10 -17
  98. data/lib/flipper/gates/boolean.rb +1 -1
  99. data/lib/flipper/gates/expression.rb +75 -0
  100. data/lib/flipper/gates/group.rb +5 -7
  101. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  102. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  103. data/lib/flipper/identifier.rb +2 -2
  104. data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
  105. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  106. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  107. data/lib/flipper/metadata.rb +5 -1
  108. data/lib/flipper/middleware/memoizer.rb +30 -14
  109. data/lib/flipper/poller.rb +117 -0
  110. data/lib/flipper/serializers/gzip.rb +24 -0
  111. data/lib/flipper/serializers/json.rb +19 -0
  112. data/lib/flipper/spec/shared_adapter_specs.rb +95 -54
  113. data/lib/flipper/test/shared_adapter_test.rb +91 -48
  114. data/lib/flipper/typecast.rb +56 -15
  115. data/lib/flipper/types/actor.rb +13 -13
  116. data/lib/flipper/types/group.rb +4 -4
  117. data/lib/flipper/types/percentage.rb +1 -1
  118. data/lib/flipper/version.rb +1 -1
  119. data/lib/flipper.rb +47 -10
  120. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  121. data/spec/flipper/adapter_builder_spec.rb +73 -0
  122. data/spec/flipper/adapter_spec.rb +30 -2
  123. data/spec/flipper/adapters/dual_write_spec.rb +2 -2
  124. data/spec/flipper/adapters/http_spec.rb +64 -8
  125. data/spec/flipper/adapters/instrumented_spec.rb +29 -11
  126. data/spec/flipper/adapters/memoizable_spec.rb +51 -31
  127. data/spec/flipper/adapters/memory_spec.rb +14 -3
  128. data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
  129. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  130. data/spec/flipper/adapters/strict_spec.rb +62 -0
  131. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
  132. data/spec/flipper/cloud/configuration_spec.rb +252 -0
  133. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  134. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  135. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  136. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +108 -0
  137. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  138. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  139. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  140. data/spec/flipper/cloud/telemetry_spec.rb +156 -0
  141. data/spec/flipper/cloud_spec.rb +180 -0
  142. data/spec/flipper/configuration_spec.rb +17 -0
  143. data/spec/flipper/dsl_spec.rb +54 -73
  144. data/spec/flipper/engine_spec.rb +291 -0
  145. data/spec/flipper/export_spec.rb +13 -0
  146. data/spec/flipper/exporter_spec.rb +16 -0
  147. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  148. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  149. data/spec/flipper/expression/builder_spec.rb +248 -0
  150. data/spec/flipper/expression_spec.rb +188 -0
  151. data/spec/flipper/expressions/all_spec.rb +15 -0
  152. data/spec/flipper/expressions/any_spec.rb +15 -0
  153. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  154. data/spec/flipper/expressions/duration_spec.rb +43 -0
  155. data/spec/flipper/expressions/equal_spec.rb +24 -0
  156. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  157. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  158. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  159. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  160. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  161. data/spec/flipper/expressions/now_spec.rb +11 -0
  162. data/spec/flipper/expressions/number_spec.rb +21 -0
  163. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  164. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  165. data/spec/flipper/expressions/property_spec.rb +13 -0
  166. data/spec/flipper/expressions/random_spec.rb +9 -0
  167. data/spec/flipper/expressions/string_spec.rb +11 -0
  168. data/spec/flipper/expressions/time_spec.rb +13 -0
  169. data/spec/flipper/feature_check_context_spec.rb +17 -17
  170. data/spec/flipper/feature_spec.rb +436 -33
  171. data/spec/flipper/gate_values_spec.rb +2 -33
  172. data/spec/flipper/gates/boolean_spec.rb +1 -1
  173. data/spec/flipper/gates/expression_spec.rb +108 -0
  174. data/spec/flipper/gates/group_spec.rb +2 -3
  175. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  176. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  177. data/spec/flipper/identifier_spec.rb +4 -5
  178. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
  179. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +25 -1
  180. data/spec/flipper/middleware/memoizer_spec.rb +67 -0
  181. data/spec/flipper/poller_spec.rb +47 -0
  182. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  183. data/spec/flipper/serializers/json_spec.rb +13 -0
  184. data/spec/flipper/typecast_spec.rb +121 -6
  185. data/spec/flipper/types/actor_spec.rb +63 -46
  186. data/spec/flipper/types/group_spec.rb +2 -2
  187. data/spec/flipper_integration_spec.rb +168 -58
  188. data/spec/flipper_spec.rb +92 -28
  189. data/spec/spec_helper.rb +6 -13
  190. data/spec/support/actor_names.yml +1 -0
  191. data/spec/support/climate_control.rb +7 -0
  192. data/spec/support/fake_backoff_policy.rb +15 -0
  193. data/spec/support/skippable.rb +18 -0
  194. data/spec/support/spec_helpers.rb +11 -3
  195. metadata +166 -13
  196. data/.github/workflows/release.yml +0 -44
  197. data/.tool-versions +0 -1
  198. data/lib/flipper/railtie.rb +0 -47
  199. data/spec/flipper/railtie_spec.rb +0 -109
@@ -0,0 +1,73 @@
1
+ RSpec.describe Flipper::AdapterBuilder do
2
+ describe "#initialize" do
3
+ it "instance_eval's block with no arg" do
4
+ called = false
5
+ self_in_block = nil
6
+
7
+ described_class.new do
8
+ called = true
9
+ self_in_block = self
10
+ end
11
+
12
+ expect(self_in_block).to be_instance_of(described_class)
13
+ expect(called).to be(true)
14
+ end
15
+
16
+ it "evals block with arg" do
17
+ called = false
18
+ self_outside_block = self
19
+ self_in_block = nil
20
+
21
+ described_class.new do |arg|
22
+ called = true
23
+ self_in_block = self
24
+ expect(arg).to be_instance_of(described_class)
25
+ end
26
+
27
+ expect(self_in_block).to be(self_outside_block)
28
+ expect(called).to be(true)
29
+ end
30
+ end
31
+
32
+ describe "#use" do
33
+ it "wraps the store adapter with the given adapter" do
34
+ subject.use(Flipper::Adapters::Memoizable)
35
+ subject.use(Flipper::Adapters::Strict, :warn)
36
+
37
+ memoizable_adapter = subject.to_adapter
38
+ strict_adapter = memoizable_adapter.adapter
39
+ memory_adapter = strict_adapter.adapter
40
+
41
+
42
+ expect(memoizable_adapter).to be_instance_of(Flipper::Adapters::Memoizable)
43
+ expect(strict_adapter).to be_instance_of(Flipper::Adapters::Strict)
44
+ expect(strict_adapter.handler).to be(Flipper::Adapters::Strict::HANDLERS.fetch(:warn))
45
+ expect(memory_adapter).to be_instance_of(Flipper::Adapters::Memory)
46
+ end
47
+
48
+ it "passes block to adapter initializer" do
49
+ expected_block = lambda {}
50
+ adapter_class = double('adapter class')
51
+
52
+ subject.use(adapter_class, &expected_block)
53
+
54
+ expect(adapter_class).to receive(:new) { |&block| expect(block).to be(expected_block) }.and_return(:adapter)
55
+ expect(subject.to_adapter).to be(:adapter)
56
+ end
57
+ end
58
+
59
+ describe "#store" do
60
+ it "defaults to memory adapter" do
61
+ expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::Memory)
62
+ end
63
+
64
+ it "only saves one store" do
65
+ require "flipper/adapters/pstore"
66
+ subject.store(Flipper::Adapters::PStore)
67
+ expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::PStore)
68
+
69
+ subject.store(Flipper::Adapters::Memory)
70
+ expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::Memory)
71
+ end
72
+ end
73
+ end
@@ -6,6 +6,7 @@ RSpec.describe Flipper::Adapter do
6
6
  boolean: nil,
7
7
  groups: Set.new,
8
8
  actors: Set.new,
9
+ expression: nil,
9
10
  percentage_of_actors: nil,
10
11
  percentage_of_time: nil,
11
12
  }
@@ -30,9 +31,9 @@ RSpec.describe Flipper::Adapter do
30
31
  end
31
32
 
32
33
  describe '#import' do
33
- it 'returns nothing' do
34
+ it 'returns true' do
34
35
  result = destination_flipper.import(source_flipper)
35
- expect(result).to be(nil)
36
+ expect(result).to be(true)
36
37
  end
37
38
 
38
39
  it 'can import from one adapter to another' do
@@ -114,5 +115,32 @@ RSpec.describe Flipper::Adapter do
114
115
  destination_flipper.import(source_flipper)
115
116
  expect(destination_flipper.features.map(&:key)).to eq([])
116
117
  end
118
+
119
+ it 'can import an export' do
120
+ source_flipper.enable(:search)
121
+ source_flipper.enable(:google_analytics, Flipper::Actor.new("User;1"))
122
+
123
+ destination_flipper.import(source_flipper.export)
124
+
125
+ feature = destination_flipper[:search]
126
+ expect(feature.boolean_value).to be(true)
127
+
128
+ feature = destination_flipper[:google_analytics]
129
+ expect(feature.actors_value).to eq(Set["User;1"])
130
+ end
131
+ end
132
+
133
+ describe "#export" do
134
+ it "exports features" do
135
+ source_flipper.enable(:search)
136
+ export = source_flipper.export
137
+ expect(export.features.dig("search", :boolean)).to eq("true")
138
+ end
139
+
140
+ it "exports with arguments" do
141
+ source_flipper.enable(:search)
142
+ export = source_flipper.export(format: :json, version: 1)
143
+ expect(export.features.dig("search", :boolean)).to eq("true")
144
+ end
117
145
  end
118
146
  end
@@ -55,14 +55,14 @@ RSpec.describe Flipper::Adapters::DualWrite do
55
55
 
56
56
  it 'updates remote and local for #enable' do
57
57
  feature = sync[:search]
58
- subject.enable feature, feature.gate(:boolean), local.boolean
58
+ subject.enable feature, feature.gate(:boolean), Flipper::Types::Boolean.new(true)
59
59
  expect(remote_adapter.count(:enable)).to be(1)
60
60
  expect(local_adapter.count(:enable)).to be(1)
61
61
  end
62
62
 
63
63
  it 'updates remote and local for #disable' do
64
64
  feature = sync[:search]
65
- subject.disable feature, feature.gate(:boolean), local.boolean(false)
65
+ subject.disable feature, feature.gate(:boolean), Flipper::Types::Boolean.new(false)
66
66
  expect(remote_adapter.count(:disable)).to be(1)
67
67
  expect(local_adapter.count(:disable)).to be(1)
68
68
  end
@@ -52,6 +52,28 @@ RSpec.describe Flipper::Adapters::Http do
52
52
  expect(flipper[:search].disable_group(:some_made_up_group)).to be(true)
53
53
  expect(flipper[:search].groups_value).to eq(Set.new)
54
54
  end
55
+
56
+ it "can import" do
57
+ adapter = Flipper::Adapters::Memory.new
58
+ source_flipper = Flipper.new(adapter)
59
+ source_flipper.enable_percentage_of_actors :search, 10
60
+ source_flipper.enable_percentage_of_time :search, 15
61
+ source_flipper.enable_actor :search, Flipper::Actor.new('User;1')
62
+ source_flipper.enable_actor :search, Flipper::Actor.new('User;100')
63
+ source_flipper.enable_group :search, :admins
64
+ source_flipper.enable_group :search, :employees
65
+ source_flipper.enable :plausible
66
+ source_flipper.disable :google_analytics
67
+
68
+ flipper = Flipper.new(subject)
69
+ flipper.import(source_flipper)
70
+ expect(flipper[:search].percentage_of_actors_value).to be(10)
71
+ expect(flipper[:search].percentage_of_time_value).to be(15)
72
+ expect(flipper[:search].actors_value).to eq(Set["User;1", "User;100"])
73
+ expect(flipper[:search].groups_value).to eq(Set["admins", "employees"])
74
+ expect(flipper[:plausible].boolean_value).to be(true)
75
+ expect(flipper[:google_analytics].boolean_value).to be(false)
76
+ end
55
77
  end
56
78
 
57
79
  it "sends default headers" do
@@ -62,16 +84,50 @@ RSpec.describe Flipper::Adapters::Http do
62
84
  }
63
85
  stub_request(:get, "http://app.com/flipper/features/feature_panel")
64
86
  .with(headers: headers)
65
- .to_return(status: 404, body: "", headers: {})
87
+ .to_return(status: 404)
66
88
 
67
89
  adapter = described_class.new(url: 'http://app.com/flipper')
68
90
  adapter.get(flipper[:feature_panel])
69
91
  end
70
92
 
93
+ it "sends framework versions" do
94
+ stub_const("Rails", double(version: "7.1.0"))
95
+ stub_const("Sinatra::VERSION", "3.1.0")
96
+ stub_const("Hanami::VERSION", "0.7.2")
97
+
98
+ headers = {
99
+ "Client-Framework" => ["rails=7.1.0", "sinatra=3.1.0", "hanami=0.7.2"]
100
+ }
101
+
102
+ stub_request(:get, "http://app.com/flipper/features/feature_panel")
103
+ .with(headers: headers)
104
+ .to_return(status: 404)
105
+
106
+ adapter = described_class.new(url: 'http://app.com/flipper')
107
+ adapter.get(flipper[:feature_panel])
108
+ end
109
+
110
+ it "does not send undefined framework versions" do
111
+ stub_const("Rails", double(version: "7.1.0"))
112
+ stub_const("Sinatra::VERSION", "3.1.0")
113
+
114
+ headers = {
115
+ "Client-Framework" => ["rails=7.1.0", "sinatra=3.1.0"]
116
+ }
117
+
118
+ stub_request(:get, "http://app.com/flipper/features/feature_panel")
119
+ .with(headers: headers)
120
+ .to_return(status: 404)
121
+
122
+ adapter = described_class.new(url: 'http://app.com/flipper')
123
+ adapter.get(flipper[:feature_panel])
124
+ end
125
+
126
+
71
127
  describe "#get" do
72
128
  it "raises error when not successful response" do
73
129
  stub_request(:get, "http://app.com/flipper/features/feature_panel")
74
- .to_return(status: 503, body: "", headers: {})
130
+ .to_return(status: 503)
75
131
 
76
132
  adapter = described_class.new(url: 'http://app.com/flipper')
77
133
  expect {
@@ -82,8 +138,8 @@ RSpec.describe Flipper::Adapters::Http do
82
138
 
83
139
  describe "#get_multi" do
84
140
  it "raises error when not successful response" do
85
- stub_request(:get, "http://app.com/flipper/features?keys=feature_panel")
86
- .to_return(status: 503, body: "", headers: {})
141
+ stub_request(:get, "http://app.com/flipper/features?keys=feature_panel&exclude_gate_names=true")
142
+ .to_return(status: 503)
87
143
 
88
144
  adapter = described_class.new(url: 'http://app.com/flipper')
89
145
  expect {
@@ -94,8 +150,8 @@ RSpec.describe Flipper::Adapters::Http do
94
150
 
95
151
  describe "#get_all" do
96
152
  it "raises error when not successful response" do
97
- stub_request(:get, "http://app.com/flipper/features")
98
- .to_return(status: 503, body: "", headers: {})
153
+ stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
154
+ .to_return(status: 503)
99
155
 
100
156
  adapter = described_class.new(url: 'http://app.com/flipper')
101
157
  expect {
@@ -106,8 +162,8 @@ RSpec.describe Flipper::Adapters::Http do
106
162
 
107
163
  describe "#features" do
108
164
  it "raises error when not successful response" do
109
- stub_request(:get, "http://app.com/flipper/features")
110
- .to_return(status: 503, body: "", headers: {})
165
+ stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
166
+ .to_return(status: 503)
111
167
 
112
168
  adapter = described_class.new(url: 'http://app.com/flipper')
113
169
  expect {
@@ -8,7 +8,7 @@ RSpec.describe Flipper::Adapters::Instrumented do
8
8
 
9
9
  let(:feature) { flipper[:stats] }
10
10
  let(:gate) { feature.gate(:percentage_of_actors) }
11
- let(:thing) { flipper.actors(22) }
11
+ let(:thing) { Flipper::Types::PercentageOfActors.new(22) }
12
12
 
13
13
  subject do
14
14
  described_class.new(adapter, instrumenter: instrumenter)
@@ -16,16 +16,6 @@ RSpec.describe Flipper::Adapters::Instrumented do
16
16
 
17
17
  it_should_behave_like 'a flipper adapter'
18
18
 
19
- it 'forwards missing methods to underlying adapter' do
20
- adapter = Class.new do
21
- def foo
22
- :foo
23
- end
24
- end.new
25
- instrumented = described_class.new(adapter)
26
- expect(instrumented.foo).to eq(:foo)
27
- end
28
-
29
19
  describe '#name' do
30
20
  it 'is instrumented' do
31
21
  expect(subject.name).to be(:instrumented)
@@ -146,4 +136,32 @@ RSpec.describe Flipper::Adapters::Instrumented do
146
136
  expect(event.payload[:result]).to be(result)
147
137
  end
148
138
  end
139
+
140
+ describe '#import' do
141
+ it 'records instrumentation' do
142
+ result = subject.import(Flipper::Adapters::Memory.new)
143
+
144
+ event = instrumenter.events.last
145
+ expect(event).not_to be_nil
146
+ expect(event.name).to eq('adapter_operation.flipper')
147
+ expect(event.payload[:operation]).to eq(:import)
148
+ expect(event.payload[:adapter_name]).to eq(:memory)
149
+ expect(event.payload[:result]).to be(result)
150
+ end
151
+ end
152
+
153
+ describe '#export' do
154
+ it 'records instrumentation' do
155
+ result = subject.export(format: :json, version: 1)
156
+
157
+ event = instrumenter.events.last
158
+ expect(event).not_to be_nil
159
+ expect(event.name).to eq('adapter_operation.flipper')
160
+ expect(event.payload[:operation]).to eq(:export)
161
+ expect(event.payload[:adapter_name]).to eq(:memory)
162
+ expect(event.payload[:format]).to be(:json)
163
+ expect(event.payload[:version]).to be(1)
164
+ expect(event.payload[:result]).to be(result)
165
+ end
166
+ end
149
167
  end
@@ -2,7 +2,7 @@ require 'flipper/adapters/memoizable'
2
2
  require 'flipper/adapters/operation_logger'
3
3
 
4
4
  RSpec.describe Flipper::Adapters::Memoizable do
5
- let(:features_key) { described_class::FeaturesKey }
5
+ let(:features_key) { :flipper_features }
6
6
  let(:adapter) { Flipper::Adapters::Memory.new }
7
7
  let(:flipper) { Flipper.new(adapter) }
8
8
  let(:cache) { {} }
@@ -11,16 +11,6 @@ RSpec.describe Flipper::Adapters::Memoizable do
11
11
 
12
12
  it_should_behave_like 'a flipper adapter'
13
13
 
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
- memoizable = described_class.new(adapter)
21
- expect(memoizable.foo).to eq(:foo)
22
- end
23
-
24
14
  describe '#name' do
25
15
  it 'is instrumented' do
26
16
  expect(subject.name).to be(:memoizable)
@@ -64,7 +54,7 @@ RSpec.describe Flipper::Adapters::Memoizable do
64
54
  it 'memoizes feature' do
65
55
  feature = flipper[:stats]
66
56
  result = subject.get(feature)
67
- expect(cache[described_class.key_for(feature.key)]).to be(result)
57
+ expect(cache["feature/#{feature.key}"]).to be(result)
68
58
  end
69
59
  end
70
60
 
@@ -93,8 +83,8 @@ RSpec.describe Flipper::Adapters::Memoizable do
93
83
  features = names.map { |name| flipper[name] }
94
84
  results = subject.get_multi(features)
95
85
  features.each do |feature|
96
- expect(cache[described_class.key_for(feature.key)]).not_to be(nil)
97
- 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])
98
88
  end
99
89
  end
100
90
  end
@@ -125,10 +115,10 @@ RSpec.describe Flipper::Adapters::Memoizable do
125
115
  features = names.map { |name| flipper[name].tap(&:enable) }
126
116
  results = subject.get_all
127
117
  features.each do |feature|
128
- expect(cache[described_class.key_for(feature.key)]).not_to be(nil)
129
- 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])
130
120
  end
131
- 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)
132
122
  end
133
123
 
134
124
  it 'only calls get_all once for memoized adapter' do
@@ -198,9 +188,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
198
188
  it 'unmemoizes feature' do
199
189
  feature = flipper[:stats]
200
190
  gate = feature.gate(:boolean)
201
- cache[described_class.key_for(feature.key)] = { some: 'thing' }
202
- subject.enable(feature, gate, flipper.bool)
203
- 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
204
194
  end
205
195
  end
206
196
 
@@ -212,8 +202,8 @@ RSpec.describe Flipper::Adapters::Memoizable do
212
202
  it 'returns result' do
213
203
  feature = flipper[:stats]
214
204
  gate = feature.gate(:boolean)
215
- result = subject.enable(feature, gate, flipper.bool)
216
- 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)
217
207
  expect(result).to eq(adapter_result)
218
208
  end
219
209
  end
@@ -228,9 +218,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
228
218
  it 'unmemoizes feature' do
229
219
  feature = flipper[:stats]
230
220
  gate = feature.gate(:boolean)
231
- cache[described_class.key_for(feature.key)] = { some: 'thing' }
232
- subject.disable(feature, gate, flipper.bool)
233
- 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
234
224
  end
235
225
  end
236
226
 
@@ -242,13 +232,43 @@ RSpec.describe Flipper::Adapters::Memoizable do
242
232
  it 'returns result' do
243
233
  feature = flipper[:stats]
244
234
  gate = feature.gate(:boolean)
245
- result = subject.disable(feature, gate, flipper.bool)
246
- 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)
247
237
  expect(result).to eq(adapter_result)
248
238
  end
249
239
  end
250
240
  end
251
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
+
252
272
  describe '#features' do
253
273
  context 'with memoization enabled' do
254
274
  before do
@@ -312,9 +332,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
312
332
 
313
333
  it 'unmemoizes the feature' do
314
334
  feature = flipper[:stats]
315
- cache[described_class.key_for(feature.key)] = { some: 'thing' }
335
+ cache["feature/#{feature.key}"] = { some: 'thing' }
316
336
  subject.remove(feature)
317
- expect(cache[described_class.key_for(feature.key)]).to be_nil
337
+ expect(cache["feature/#{feature.key}"]).to be_nil
318
338
  end
319
339
  end
320
340
 
@@ -337,9 +357,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
337
357
 
338
358
  it 'unmemoizes feature' do
339
359
  feature = flipper[:stats]
340
- cache[described_class.key_for(feature.key)] = { some: 'thing' }
360
+ cache["feature/#{feature.key}"] = { some: 'thing' }
341
361
  subject.clear(feature)
342
- expect(cache[described_class.key_for(feature.key)]).to be_nil
362
+ expect(cache["feature/#{feature.key}"]).to be_nil
343
363
  end
344
364
  end
345
365
 
@@ -1,8 +1,17 @@
1
1
  RSpec.describe Flipper::Adapters::Memory do
2
2
  let(:source) { {} }
3
- subject { described_class.new(source) }
4
3
 
5
- it_should_behave_like 'a flipper adapter'
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
6
15
 
7
16
  it "can initialize from big hash" do
8
17
  flipper = Flipper.new(subject)
@@ -14,7 +23,9 @@ RSpec.describe Flipper::Adapters::Memory do
14
23
  flipper.enable_actor :following, Flipper::Actor.new('3')
15
24
  flipper.enable_group :following, Flipper::Types::Group.new(:staff)
16
25
 
17
- expect(source).to eq({
26
+ dup = described_class.new(subject.get_all)
27
+
28
+ expect(dup.get_all).to eq({
18
29
  "subscriptions" => subject.default_config.merge(boolean: "true"),
19
30
  "search" => subject.default_config,
20
31
  "logging" => subject.default_config.merge(:percentage_of_time => "30"),
@@ -18,16 +18,6 @@ RSpec.describe Flipper::Adapters::OperationLogger do
18
18
  expect(output).to match(/@adapter=#<Flipper::Adapters::Memory/)
19
19
  end
20
20
 
21
- it 'forwards missing methods to underlying adapter' do
22
- adapter = Class.new do
23
- def foo
24
- :foo
25
- end
26
- end.new
27
- operation_logger = described_class.new(adapter)
28
- expect(operation_logger.foo).to eq(:foo)
29
- end
30
-
31
21
  describe '#get' do
32
22
  before do
33
23
  @feature = flipper[:stats]
@@ -47,7 +37,7 @@ RSpec.describe Flipper::Adapters::OperationLogger do
47
37
  before do
48
38
  @feature = flipper[:stats]
49
39
  @gate = @feature.gate(:boolean)
50
- @thing = flipper.bool
40
+ @thing = Flipper::Types::Boolean.new
51
41
  @result = subject.enable(@feature, @gate, @thing)
52
42
  end
53
43
 
@@ -64,7 +54,7 @@ RSpec.describe Flipper::Adapters::OperationLogger do
64
54
  before do
65
55
  @feature = flipper[:stats]
66
56
  @gate = @feature.gate(:boolean)
67
- @thing = flipper.bool
57
+ @thing = Flipper::Types::Boolean.new
68
58
  @result = subject.disable(@feature, @gate, @thing)
69
59
  end
70
60
 
@@ -106,4 +96,33 @@ RSpec.describe Flipper::Adapters::OperationLogger do
106
96
  expect(@result).to eq(adapter.add(@feature))
107
97
  end
108
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
109
128
  end
@@ -5,11 +5,12 @@ RSpec.describe Flipper::Adapters::ReadOnly do
5
5
  let(:flipper) { Flipper.new(subject) }
6
6
  let(:feature) { flipper[:stats] }
7
7
 
8
- let(:boolean_gate) { feature.gate(:boolean) }
9
- let(:group_gate) { feature.gate(:group) }
10
- let(:actor_gate) { feature.gate(:actor) }
11
- let(:actors_gate) { feature.gate(:percentage_of_actors) }
12
- 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) }
13
14
 
14
15
  subject { described_class.new(adapter) }
15
16
 
@@ -41,18 +42,28 @@ RSpec.describe Flipper::Adapters::ReadOnly do
41
42
  end
42
43
 
43
44
  it 'can get feature' do
45
+ expression = Flipper.property(:plan).eq("basic")
44
46
  actor22 = Flipper::Actor.new('22')
45
- adapter.enable(feature, boolean_gate, flipper.boolean)
47
+ adapter.enable(feature, boolean_gate, Flipper::Types::Boolean.new)
46
48
  adapter.enable(feature, group_gate, flipper.group(:admins))
47
- adapter.enable(feature, actor_gate, flipper.actor(actor22))
48
- adapter.enable(feature, actors_gate, flipper.actors(25))
49
- adapter.enable(feature, time_gate, flipper.time(45))
50
-
51
- expect(subject.get(feature)).to eq(boolean: 'true',
52
- groups: Set['admins'],
53
- actors: Set['22'],
54
- percentage_of_actors: '25',
55
- 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
+ })
56
67
  end
57
68
 
58
69
  it 'can get features' do
@@ -61,6 +72,10 @@ RSpec.describe Flipper::Adapters::ReadOnly do
61
72
  expect(subject.features).to eq(Set['stats'])
62
73
  end
63
74
 
75
+ it 'is configured as read only' do
76
+ expect(subject.read_only?).to eq(true)
77
+ end
78
+
64
79
  it 'raises error on add' do
65
80
  expect { subject.add(feature) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
66
81
  end
@@ -74,12 +89,12 @@ RSpec.describe Flipper::Adapters::ReadOnly do
74
89
  end
75
90
 
76
91
  it 'raises error on enable' do
77
- expect { subject.enable(feature, boolean_gate, flipper.boolean) }
92
+ expect { subject.enable(feature, boolean_gate, Flipper::Types::Boolean.new) }
78
93
  .to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
79
94
  end
80
95
 
81
96
  it 'raises error on disable' do
82
- expect { subject.disable(feature, boolean_gate, flipper.boolean) }
97
+ expect { subject.disable(feature, boolean_gate, Flipper::Types::Boolean.new) }
83
98
  .to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
84
99
  end
85
100
  end