flipper 0.12.2 → 0.24.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +1 -0
  3. data/.github/workflows/ci.yml +62 -0
  4. data/.github/workflows/examples.yml +67 -0
  5. data/.rspec +1 -0
  6. data/Changelog.md +263 -3
  7. data/Dockerfile +1 -1
  8. data/Gemfile +13 -16
  9. data/README.md +59 -56
  10. data/Rakefile +10 -2
  11. data/docker-compose.yml +37 -34
  12. data/docs/DockerCompose.md +0 -1
  13. data/docs/README.md +1 -0
  14. data/docs/images/banner.jpg +0 -0
  15. data/examples/api/basic.ru +19 -0
  16. data/examples/api/custom_memoized.ru +37 -0
  17. data/examples/api/memoized.ru +43 -0
  18. data/examples/basic.rb +1 -13
  19. data/examples/configuring_default.rb +2 -6
  20. data/examples/dsl.rb +13 -25
  21. data/examples/enabled_for_actor.rb +8 -16
  22. data/examples/group.rb +3 -7
  23. data/examples/group_dynamic_lookup.rb +5 -20
  24. data/examples/group_with_members.rb +4 -15
  25. data/examples/importing.rb +1 -1
  26. data/examples/individual_actor.rb +2 -6
  27. data/examples/instrumentation.rb +1 -3
  28. data/examples/instrumentation_last_accessed_at.rb +37 -0
  29. data/examples/memoizing.rb +35 -0
  30. data/examples/percentage_of_actors.rb +6 -17
  31. data/examples/percentage_of_actors_enabled_check.rb +7 -11
  32. data/examples/percentage_of_actors_group.rb +5 -19
  33. data/examples/percentage_of_time.rb +3 -7
  34. data/flipper.gemspec +2 -1
  35. data/lib/flipper/actor.rb +4 -0
  36. data/lib/flipper/adapter.rb +7 -42
  37. data/lib/flipper/adapters/dual_write.rb +61 -0
  38. data/lib/flipper/adapters/failover.rb +83 -0
  39. data/lib/flipper/adapters/http/client.rb +23 -1
  40. data/lib/flipper/adapters/http/error.rb +19 -1
  41. data/lib/flipper/adapters/http.rb +32 -28
  42. data/lib/flipper/adapters/instrumented.rb +20 -19
  43. data/lib/flipper/adapters/memoizable.rb +8 -16
  44. data/lib/flipper/adapters/memory.rb +24 -95
  45. data/lib/flipper/adapters/operation_logger.rb +19 -2
  46. data/lib/flipper/adapters/pstore.rb +12 -4
  47. data/lib/flipper/adapters/read_only.rb +2 -0
  48. data/lib/flipper/adapters/sync/feature_synchronizer.rb +118 -0
  49. data/lib/flipper/adapters/sync/interval_synchronizer.rb +49 -0
  50. data/lib/flipper/adapters/sync/synchronizer.rb +63 -0
  51. data/lib/flipper/adapters/sync.rb +91 -0
  52. data/lib/flipper/configuration.rb +33 -7
  53. data/lib/flipper/dsl.rb +22 -5
  54. data/lib/flipper/errors.rb +17 -2
  55. data/lib/flipper/feature.rb +9 -4
  56. data/lib/flipper/gate_values.rb +1 -0
  57. data/lib/flipper/identifier.rb +17 -0
  58. data/lib/flipper/instrumenters/memory.rb +18 -2
  59. data/lib/flipper/metadata.rb +5 -0
  60. data/lib/flipper/middleware/memoizer.rb +31 -18
  61. data/lib/flipper/middleware/setup_env.rb +13 -3
  62. data/lib/flipper/railtie.rb +47 -0
  63. data/lib/flipper/spec/shared_adapter_specs.rb +27 -1
  64. data/lib/flipper/test/shared_adapter_test.rb +29 -2
  65. data/lib/flipper/type.rb +0 -7
  66. data/lib/flipper/typecast.rb +2 -0
  67. data/lib/flipper/types/actor.rb +8 -2
  68. data/lib/flipper/types/boolean.rb +2 -0
  69. data/lib/flipper/types/group.rb +8 -1
  70. data/lib/flipper/types/percentage.rb +2 -0
  71. data/lib/flipper/version.rb +1 -1
  72. data/lib/flipper.rb +22 -5
  73. data/spec/flipper/actor_spec.rb +10 -2
  74. data/spec/flipper/adapter_spec.rb +2 -5
  75. data/spec/flipper/adapters/dual_write_spec.rb +69 -0
  76. data/spec/flipper/adapters/failover_spec.rb +129 -0
  77. data/spec/flipper/adapters/http_spec.rb +114 -11
  78. data/spec/flipper/adapters/instrumented_spec.rb +2 -3
  79. data/spec/flipper/adapters/memoizable_spec.rb +0 -3
  80. data/spec/flipper/adapters/memory_spec.rb +21 -5
  81. data/spec/flipper/adapters/operation_logger_spec.rb +9 -3
  82. data/spec/flipper/adapters/pstore_spec.rb +0 -2
  83. data/spec/flipper/adapters/read_only_spec.rb +0 -1
  84. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +208 -0
  85. data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +33 -0
  86. data/spec/flipper/adapters/sync/synchronizer_spec.rb +88 -0
  87. data/spec/flipper/adapters/sync_spec.rb +200 -0
  88. data/spec/flipper/configuration_spec.rb +20 -3
  89. data/spec/flipper/dsl_spec.rb +24 -5
  90. data/spec/flipper/feature_check_context_spec.rb +2 -4
  91. data/spec/flipper/feature_spec.rb +30 -10
  92. data/spec/flipper/gate_spec.rb +0 -2
  93. data/spec/flipper/gate_values_spec.rb +0 -1
  94. data/spec/flipper/gates/actor_spec.rb +0 -2
  95. data/spec/flipper/gates/boolean_spec.rb +0 -2
  96. data/spec/flipper/gates/group_spec.rb +0 -2
  97. data/spec/flipper/gates/percentage_of_actors_spec.rb +0 -2
  98. data/spec/flipper/gates/percentage_of_time_spec.rb +0 -2
  99. data/spec/flipper/identifier_spec.rb +13 -0
  100. data/spec/flipper/instrumentation/log_subscriber_spec.rb +0 -2
  101. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +0 -2
  102. data/spec/flipper/instrumenters/memory_spec.rb +18 -1
  103. data/spec/flipper/instrumenters/noop_spec.rb +14 -9
  104. data/spec/flipper/middleware/memoizer_spec.rb +94 -39
  105. data/spec/flipper/middleware/setup_env_spec.rb +23 -5
  106. data/spec/flipper/railtie_spec.rb +73 -0
  107. data/spec/flipper/registry_spec.rb +0 -1
  108. data/spec/flipper/typecast_spec.rb +0 -1
  109. data/spec/flipper/types/actor_spec.rb +0 -1
  110. data/spec/flipper/types/boolean_spec.rb +0 -1
  111. data/spec/flipper/types/group_spec.rb +22 -1
  112. data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
  113. data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
  114. data/spec/flipper/types/percentage_spec.rb +0 -1
  115. data/spec/{integration_spec.rb → flipper_integration_spec.rb} +0 -2
  116. data/spec/flipper_spec.rb +30 -1
  117. data/spec/{helper.rb → spec_helper.rb} +5 -3
  118. data/spec/support/descriptions.yml +1 -0
  119. data/spec/support/spec_helpers.rb +22 -2
  120. data/test/adapters/memory_test.rb +0 -1
  121. data/test/test_helper.rb +2 -1
  122. data/{test → test_rails}/helper.rb +1 -1
  123. metadata +48 -25
  124. data/.rubocop.yml +0 -48
  125. data/.rubocop_todo.yml +0 -199
  126. data/docs/Adapters.md +0 -125
  127. data/docs/Caveats.md +0 -4
  128. data/docs/Gates.md +0 -167
  129. data/docs/Instrumentation.md +0 -27
  130. data/docs/Optimization.md +0 -119
  131. data/docs/api/README.md +0 -849
  132. data/docs/http/README.md +0 -35
  133. data/docs/read-only/README.md +0 -22
  134. data/examples/example_setup.rb +0 -8
@@ -0,0 +1,33 @@
1
+ require "flipper/adapters/sync/interval_synchronizer"
2
+
3
+ RSpec.describe Flipper::Adapters::Sync::IntervalSynchronizer do
4
+ let(:events) { [] }
5
+ let(:synchronizer) { -> { events << now } }
6
+ let(:interval) { 10 }
7
+ let(:now) { subject.send(:now) }
8
+
9
+ subject { described_class.new(synchronizer, interval: interval) }
10
+
11
+ it 'synchronizes on first call' do
12
+ expect(events.size).to be(0)
13
+ subject.call
14
+ expect(events.size).to be(1)
15
+ end
16
+
17
+ it "only invokes wrapped synchronizer every interval seconds" do
18
+ subject.call
19
+ events.clear
20
+
21
+ # move time to one millisecond less than last sync + interval
22
+ 1.upto(interval) do |i|
23
+ allow(subject).to receive(:now).and_return(now + i - 1)
24
+ subject.call
25
+ end
26
+ expect(events.size).to be(0)
27
+
28
+ # move time to last sync + interval in milliseconds
29
+ allow(subject).to receive(:now).and_return(now + interval)
30
+ subject.call
31
+ expect(events.size).to be(1)
32
+ end
33
+ end
@@ -0,0 +1,88 @@
1
+ require "flipper/adapters/memory"
2
+ require "flipper/instrumenters/memory"
3
+ require "flipper/adapters/sync/synchronizer"
4
+
5
+ RSpec.describe Flipper::Adapters::Sync::Synchronizer do
6
+ let(:local) { Flipper::Adapters::Memory.new }
7
+ let(:remote) { Flipper::Adapters::Memory.new }
8
+ let(:local_flipper) { Flipper.new(local) }
9
+ let(:remote_flipper) { Flipper.new(remote) }
10
+ let(:instrumenter) { Flipper::Instrumenters::Memory.new }
11
+
12
+ subject { described_class.new(local, remote, instrumenter: instrumenter) }
13
+
14
+ it "instruments call" do
15
+ subject.call
16
+ expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
17
+
18
+ events = instrumenter.events_by_name("synchronizer_call.flipper")
19
+ expect(events.size).to be(1)
20
+ end
21
+
22
+ it "raises errors by default" do
23
+ exception = StandardError.new
24
+ expect(remote).to receive(:get_all).and_raise(exception)
25
+
26
+ expect { subject.call }.to raise_error(exception)
27
+ end
28
+
29
+ context "when raise disabled" do
30
+ subject do
31
+ options = {
32
+ instrumenter: instrumenter,
33
+ raise: false,
34
+ }
35
+ described_class.new(local, remote, options)
36
+ end
37
+
38
+ it "does not raise, but instruments exceptions for visibility" do
39
+ exception = StandardError.new
40
+ expect(remote).to receive(:get_all).and_raise(exception)
41
+
42
+ expect { subject.call }.not_to raise_error
43
+
44
+ events = instrumenter.events_by_name("synchronizer_exception.flipper")
45
+ expect(events.size).to be(1)
46
+
47
+ event = events[0]
48
+ expect(event.payload[:exception]).to eq(exception)
49
+ end
50
+ end
51
+
52
+ describe '#call' do
53
+ it 'returns nothing' do
54
+ expect(subject.call).to be(nil)
55
+ expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
56
+ end
57
+
58
+ it 'syncs each remote feature to local' do
59
+ remote_flipper.enable(:search)
60
+ remote_flipper.enable_percentage_of_time(:logging, 10)
61
+
62
+ subject.call
63
+ expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
64
+
65
+ expect(local_flipper[:search].boolean_value).to eq(true)
66
+ expect(local_flipper[:logging].percentage_of_time_value).to eq(10)
67
+ expect(local_flipper.features.map(&:key).sort).to eq(%w(logging search))
68
+ end
69
+
70
+ it 'adds features in remote that are not in local' do
71
+ remote_flipper.add(:search)
72
+
73
+ subject.call
74
+ expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
75
+
76
+ expect(local_flipper.features.map(&:key)).to eq(["search"])
77
+ end
78
+
79
+ it 'removes features in local that are not in remote' do
80
+ local_flipper.add(:stats)
81
+
82
+ subject.call
83
+ expect(instrumenter.events_by_name("synchronizer_exception.flipper").size).to be(0)
84
+
85
+ expect(local_flipper.features.map(&:key)).to eq([])
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,200 @@
1
+ require 'flipper/adapters/sync'
2
+ require 'flipper/adapters/operation_logger'
3
+ require 'active_support/notifications'
4
+
5
+ RSpec.describe Flipper::Adapters::Sync do
6
+ let(:local_adapter) do
7
+ Flipper::Adapters::OperationLogger.new Flipper::Adapters::Memory.new
8
+ end
9
+ let(:remote_adapter) do
10
+ Flipper::Adapters::OperationLogger.new Flipper::Adapters::Memory.new
11
+ end
12
+ let(:local) { Flipper.new(local_adapter) }
13
+ let(:remote) { Flipper.new(remote_adapter) }
14
+ let(:sync) { Flipper.new(subject) }
15
+
16
+ subject do
17
+ described_class.new(local_adapter, remote_adapter, interval: 1)
18
+ end
19
+
20
+ it_should_behave_like 'a flipper adapter'
21
+
22
+ context 'when local has never been synced' do
23
+ it 'syncs boolean' do
24
+ remote.enable(:search)
25
+ expect(sync[:search].boolean_value).to be(true)
26
+ expect(subject.features.sort).to eq(%w(search))
27
+ end
28
+
29
+ it 'syncs actor' do
30
+ actor = Flipper::Actor.new("User;1000")
31
+ remote.enable_actor(:search, actor)
32
+ expect(sync[:search].actors_value).to eq(Set[actor.flipper_id])
33
+ expect(subject.features.sort).to eq(%w(search))
34
+ end
35
+
36
+ it 'syncs group' do
37
+ remote.enable_group(:search, :staff)
38
+ expect(sync[:search].groups_value).to eq(Set["staff"])
39
+ expect(subject.features.sort).to eq(%w(search))
40
+ end
41
+
42
+ it 'syncs percentage of actors' do
43
+ remote.enable_percentage_of_actors(:search, 25)
44
+ expect(sync[:search].percentage_of_actors_value).to eq(25)
45
+ expect(subject.features.sort).to eq(%w(search))
46
+ end
47
+
48
+ it 'syncs percentage of time' do
49
+ remote.enable_percentage_of_time(:search, 15)
50
+ expect(sync[:search].percentage_of_time_value).to eq(15)
51
+ expect(subject.features.sort).to eq(%w(search))
52
+ end
53
+ end
54
+
55
+ it 'enables boolean locally when remote feature boolean enabled' do
56
+ remote.disable(:search)
57
+ local.disable(:search)
58
+ remote.enable(:search)
59
+ subject # initialize forces sync
60
+ expect(local[:search].boolean_value).to be(true)
61
+ end
62
+
63
+ it 'disables boolean locally when remote feature disabled' do
64
+ remote.enable(:search)
65
+ local.enable(:search)
66
+ remote.disable(:search)
67
+ subject # initialize forces sync
68
+ expect(local[:search].boolean_value).to be(false)
69
+ end
70
+
71
+ it 'adds local actor when remote actor is added' do
72
+ actor = Flipper::Actor.new("User;235")
73
+ remote.enable_actor(:search, actor)
74
+ subject # initialize forces sync
75
+ expect(local[:search].actors_value).to eq(Set[actor.flipper_id])
76
+ end
77
+
78
+ it 'removes local actor when remote actor is removed' do
79
+ actor = Flipper::Actor.new("User;235")
80
+ remote.enable_actor(:search, actor)
81
+ local.enable_actor(:search, actor)
82
+ remote.disable(:search, actor)
83
+ subject # initialize forces sync
84
+ expect(local[:search].actors_value).to eq(Set.new)
85
+ end
86
+
87
+ it 'adds local group when remote group is added' do
88
+ group = Flipper::Types::Group.new(:staff)
89
+ remote.enable_group(:search, group)
90
+ subject # initialize forces sync
91
+ expect(local[:search].groups_value).to eq(Set["staff"])
92
+ end
93
+
94
+ it 'removes local group when remote group is removed' do
95
+ group = Flipper::Types::Group.new(:staff)
96
+ remote.enable_group(:search, group)
97
+ local.enable_group(:search, group)
98
+ remote.disable(:search, group)
99
+ subject # initialize forces sync
100
+ expect(local[:search].groups_value).to eq(Set.new)
101
+ end
102
+
103
+ it 'updates percentage of actors when remote is updated' do
104
+ remote.enable_percentage_of_actors(:search, 10)
105
+ local.enable_percentage_of_actors(:search, 10)
106
+ remote.enable_percentage_of_actors(:search, 15)
107
+ subject # initialize forces sync
108
+ expect(local[:search].percentage_of_actors_value).to eq(15)
109
+ end
110
+
111
+ it 'updates percentage of time when remote is updated' do
112
+ remote.enable_percentage_of_time(:search, 10)
113
+ local.enable_percentage_of_time(:search, 10)
114
+ remote.enable_percentage_of_time(:search, 15)
115
+ subject # initialize forces sync
116
+ expect(local[:search].percentage_of_time_value).to eq(15)
117
+ end
118
+
119
+ context 'when local and remote match' do
120
+ it 'does not update boolean enabled' do
121
+ local.enable(:search)
122
+ remote.enable(:search)
123
+ local_adapter.reset
124
+ subject # initialize forces sync
125
+ expect(local_adapter.count(:enable)).to be(0)
126
+ end
127
+
128
+ it 'does not update boolean disabled' do
129
+ local.disable(:search)
130
+ remote.disable(:search)
131
+ local_adapter.reset
132
+ subject # initialize forces sync
133
+ expect(local_adapter.count(:disable)).to be(0)
134
+ end
135
+
136
+ it 'does not update actors' do
137
+ actor = Flipper::Actor.new("User;235")
138
+ local.enable_actor(:search, actor)
139
+ remote.enable_actor(:search, actor)
140
+ local_adapter.reset
141
+ subject # initialize forces sync
142
+ expect(local_adapter.count(:enable)).to be(0)
143
+ expect(local_adapter.count(:disable)).to be(0)
144
+ end
145
+
146
+ it 'does not update groups' do
147
+ group = Flipper::Types::Group.new(:staff)
148
+ local.enable_group(:search, group)
149
+ remote.enable_group(:search, group)
150
+ local_adapter.reset
151
+ subject # initialize forces sync
152
+ expect(local_adapter.count(:enable)).to be(0)
153
+ expect(local_adapter.count(:disable)).to be(0)
154
+ end
155
+
156
+ it 'does not update percentage of actors' do
157
+ local.enable_percentage_of_actors(:search, 10)
158
+ remote.enable_percentage_of_actors(:search, 10)
159
+ local_adapter.reset
160
+ subject # initialize forces sync
161
+ expect(local_adapter.count(:enable)).to be(0)
162
+ expect(local_adapter.count(:disable)).to be(0)
163
+ end
164
+
165
+ it 'does not update percentage of time' do
166
+ local.enable_percentage_of_time(:search, 10)
167
+ remote.enable_percentage_of_time(:search, 10)
168
+ local_adapter.reset
169
+ subject # initialize forces sync
170
+ expect(local_adapter.count(:enable)).to be(0)
171
+ expect(local_adapter.count(:disable)).to be(0)
172
+ end
173
+ end
174
+
175
+ it 'synchronizes for #features' do
176
+ expect(subject).to receive(:synchronize)
177
+ subject.features
178
+ end
179
+
180
+ it 'synchronizes for #get' do
181
+ expect(subject).to receive(:synchronize)
182
+ subject.get sync[:search]
183
+ end
184
+
185
+ it 'synchronizes for #get_multi' do
186
+ expect(subject).to receive(:synchronize)
187
+ subject.get_multi [sync[:search]]
188
+ end
189
+
190
+ it 'synchronizes for #get_all' do
191
+ expect(subject).to receive(:synchronize)
192
+ subject.get_all
193
+ end
194
+
195
+ it 'does not raise sync exceptions' do
196
+ exception = StandardError.new
197
+ expect(remote_adapter).to receive(:get_all).and_raise(exception)
198
+ expect { subject.get_all }.not_to raise_error
199
+ end
200
+ end
@@ -1,14 +1,31 @@
1
- require 'helper'
2
1
  require 'flipper/configuration'
3
2
 
4
3
  RSpec.describe Flipper::Configuration do
4
+ describe '#adapter' do
5
+ it 'returns instance using Memory adapter' do
6
+ expect(subject.adapter).to be_a(Flipper::Adapters::Memory)
7
+ end
8
+
9
+ it 'can be set' do
10
+ instance = Flipper::Adapters::Memory.new
11
+ expect(subject.adapter).not_to be(instance)
12
+ subject.adapter { instance }
13
+ expect(subject.adapter).to be(instance)
14
+ # All adapters are wrapped in Memoizable
15
+ expect(subject.default.adapter.adapter).to be(instance)
16
+ end
17
+ end
18
+
5
19
  describe '#default' do
6
- it 'raises if default not configured' do
7
- expect { subject.default }.to raise_error(Flipper::DefaultNotSet)
20
+ it 'returns instance using Memory adapter' do
21
+ expect(subject.default).to be_a(Flipper::DSL)
22
+ # All adapters are wrapped in Memoizable
23
+ expect(subject.default.adapter.adapter).to be_a(Flipper::Adapters::Memory)
8
24
  end
9
25
 
10
26
  it 'can be set default' do
11
27
  instance = Flipper.new(Flipper::Adapters::Memory.new)
28
+ expect(subject.default).not_to be(instance)
12
29
  subject.default { instance }
13
30
  expect(subject.default).to be(instance)
14
31
  end
@@ -1,6 +1,4 @@
1
- require 'helper'
2
1
  require 'flipper/dsl'
3
- require 'flipper/adapters/memory'
4
2
 
5
3
  RSpec.describe Flipper::DSL do
6
4
  subject { described_class.new(adapter) }
@@ -8,9 +6,19 @@ RSpec.describe Flipper::DSL do
8
6
  let(:adapter) { Flipper::Adapters::Memory.new }
9
7
 
10
8
  describe '#initialize' do
11
- it 'sets adapter' do
12
- dsl = described_class.new(adapter)
13
- expect(dsl.adapter).not_to be_nil
9
+ context 'when using default memoize strategy' do
10
+ it 'wraps the given adapter with Flipper::Adapters::Memoizable' do
11
+ dsl = described_class.new(adapter)
12
+ expect(dsl.adapter.class).to be(Flipper::Adapters::Memoizable)
13
+ expect(dsl.adapter.adapter).to be(adapter)
14
+ end
15
+ end
16
+
17
+ context 'when disabling memoization' do
18
+ it 'uses the given adapter directly' do
19
+ dsl = described_class.new(adapter, memoize: false)
20
+ expect(dsl.adapter).to be(adapter)
21
+ end
14
22
  end
15
23
 
16
24
  it 'defaults instrumenter to noop' do
@@ -313,6 +321,17 @@ RSpec.describe Flipper::DSL do
313
321
  end
314
322
  end
315
323
 
324
+ describe '#exist?' do
325
+ it 'returns true if the feature is added in adapter' do
326
+ subject.add(:stats)
327
+ expect(subject.exist?(:stats)).to be(true)
328
+ end
329
+
330
+ it 'returns false if the feature is NOT added in adapter' do
331
+ expect(subject.exist?(:stats)).to be(false)
332
+ end
333
+ end
334
+
316
335
  describe '#remove' do
317
336
  it 'removes the feature' do
318
337
  subject.adapter.add(subject[:stats])
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::FeatureCheckContext do
4
2
  let(:feature_name) { :new_profiles }
5
3
  let(:values) { Flipper::GateValues.new({}) }
@@ -41,8 +39,8 @@ RSpec.describe Flipper::FeatureCheckContext do
41
39
  end
42
40
 
43
41
  it 'knows actors_value' do
44
- args = options.merge(values: Flipper::GateValues.new(actors: Set['User:1']))
45
- expect(described_class.new(args).actors_value).to eq(Set['User:1'])
42
+ args = options.merge(values: Flipper::GateValues.new(actors: Set['User;1']))
43
+ expect(described_class.new(args).actors_value).to eq(Set['User;1'])
46
44
  end
47
45
 
48
46
  it 'knows groups_value' do
@@ -1,6 +1,4 @@
1
- require 'helper'
2
1
  require 'flipper/feature'
3
- require 'flipper/adapters/memory'
4
2
  require 'flipper/instrumenters/memory'
5
3
 
6
4
  RSpec.describe Flipper::Feature do
@@ -97,6 +95,17 @@ RSpec.describe Flipper::Feature do
97
95
  end
98
96
  end
99
97
 
98
+ describe '#exist?' do
99
+ it 'returns true if feature is added in adapter' do
100
+ subject.add
101
+ expect(subject.exist?).to be(true)
102
+ end
103
+
104
+ it 'returns false if feature is NOT added in adapter' do
105
+ expect(subject.exist?).to be(false)
106
+ end
107
+ end
108
+
100
109
  describe '#remove' do
101
110
  it 'removes feature from adapter' do
102
111
  adapter.add(subject)
@@ -232,6 +241,17 @@ RSpec.describe Flipper::Feature do
232
241
  expect(event.payload[:result]).not_to be_nil
233
242
  end
234
243
 
244
+ it 'is recorded for exist?' do
245
+ subject.exist?
246
+
247
+ event = instrumenter.events.last
248
+ expect(event).not_to be_nil
249
+ expect(event.name).to eq('feature_operation.flipper')
250
+ expect(event.payload[:feature_name]).to eq(:search)
251
+ expect(event.payload[:operation]).to eq(:exist?)
252
+ expect(event.payload[:result]).not_to be_nil
253
+ end
254
+
235
255
  it 'is recorded for remove' do
236
256
  subject.remove
237
257
 
@@ -338,19 +358,19 @@ RSpec.describe Flipper::Feature do
338
358
  end
339
359
 
340
360
  it 'returns :on' do
341
- expect(subject.state).to be(:on)
361
+ expect(subject.state).to be(:conditional)
342
362
  end
343
363
 
344
- it 'returns true for on?' do
345
- expect(subject.on?).to be(true)
364
+ it 'returns false for on?' do
365
+ expect(subject.on?).to be(false)
346
366
  end
347
367
 
348
368
  it 'returns false for off?' do
349
369
  expect(subject.off?).to be(false)
350
370
  end
351
371
 
352
- it 'returns false for conditional?' do
353
- expect(subject.conditional?).to be(false)
372
+ it 'returns true for conditional?' do
373
+ expect(subject.conditional?).to be(true)
354
374
  end
355
375
  end
356
376
 
@@ -509,12 +529,12 @@ RSpec.describe Flipper::Feature do
509
529
 
510
530
  context 'when one or more actors are enabled' do
511
531
  before do
512
- subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User:5'))
513
- subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User:22'))
532
+ subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User;5'))
533
+ subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User;22'))
514
534
  end
515
535
 
516
536
  it 'returns set of actor ids' do
517
- expect(subject.actors_value).to eq(Set.new(['User:5', 'User:22']))
537
+ expect(subject.actors_value).to eq(Set.new(['User;5', 'User;22']))
518
538
  end
519
539
  end
520
540
  end
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Gate do
4
2
  let(:feature_name) { :stats }
5
3
 
@@ -1,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/gate_values'
3
2
 
4
3
  RSpec.describe Flipper::GateValues do
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Gates::Actor do
4
2
  let(:feature_name) { :search }
5
3
 
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Gates::Boolean do
4
2
  let(:feature_name) { :search }
5
3
 
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Gates::Group do
4
2
  let(:feature_name) { :search }
5
3
 
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Gates::PercentageOfActors do
4
2
  let(:feature_name) { :search }
5
3
 
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::Gates::PercentageOfTime do
4
2
  let(:feature_name) { :search }
5
3
 
@@ -0,0 +1,13 @@
1
+ require 'flipper/identifier'
2
+
3
+ RSpec.describe Flipper::Identifier do
4
+ describe '#flipper_id' do
5
+ class User < Struct.new(:id)
6
+ include Flipper::Identifier
7
+ end
8
+
9
+ it 'uses class name and id' do
10
+ expect(User.new(5).flipper_id).to eq('User;5')
11
+ end
12
+ end
13
+ end
@@ -1,7 +1,5 @@
1
1
  require 'logger'
2
- require 'helper'
3
2
  require 'flipper/adapters/instrumented'
4
- require 'flipper/adapters/memory'
5
3
  require 'flipper/instrumentation/log_subscriber'
6
4
 
7
5
  RSpec.describe Flipper::Instrumentation::LogSubscriber do
@@ -1,5 +1,3 @@
1
- require 'helper'
2
- require 'flipper/adapters/memory'
3
1
  require 'flipper/adapters/instrumented'
4
2
  require 'flipper/instrumentation/statsd'
5
3
  require 'statsd'
@@ -1,4 +1,3 @@
1
- require 'helper'
2
1
  require 'flipper/instrumenters/memory'
3
2
 
4
3
  RSpec.describe Flipper::Instrumenters::Memory do
@@ -22,5 +21,23 @@ RSpec.describe Flipper::Instrumenters::Memory do
22
21
  event = described_class::Event.new(name, payload, block_result)
23
22
  expect(instrumenter.events).to eq([event])
24
23
  end
24
+
25
+ context 'when an error is raised' do
26
+ subject do
27
+ instrumenter.instrument(:name) { raise IOError }
28
+ end
29
+
30
+ let(:instrumenter) { described_class.new }
31
+
32
+ it 'captures and propagates the error' do
33
+ expect { subject }.to raise_error(IOError)
34
+
35
+ expect(instrumenter.events.count).to be 1
36
+
37
+ payload = instrumenter.events[0].payload
38
+ expect(payload.keys).to include(:exception, :exception_object)
39
+ expect(payload[:exception_object]).to be_a IOError
40
+ end
41
+ end
25
42
  end
26
43
  end
@@ -1,21 +1,26 @@
1
- require 'helper'
2
- require 'flipper/instrumenters/noop'
3
-
4
1
  RSpec.describe Flipper::Instrumenters::Noop do
5
2
  describe '.instrument' do
6
3
  context 'with name' do
7
4
  it 'yields block' do
8
- yielded = false
9
- described_class.instrument(:foo) { yielded = true }
10
- expect(yielded).to eq(true)
5
+ expect { |block|
6
+ described_class.instrument(:foo, &block)
7
+ }.to yield_control
11
8
  end
12
9
  end
13
10
 
14
11
  context 'with name and payload' do
12
+ let(:payload) { { pay: :load } }
13
+
15
14
  it 'yields block' do
16
- yielded = false
17
- described_class.instrument(:foo, pay: :load) { yielded = true }
18
- expect(yielded).to eq(true)
15
+ expect { |block|
16
+ described_class.instrument(:foo, payload, &block)
17
+ }.to yield_control
18
+ end
19
+
20
+ it 'yields the payload' do
21
+ described_class.instrument(:foo, payload) do |block_payload|
22
+ expect(block_payload).to eq payload
23
+ end
19
24
  end
20
25
  end
21
26
  end