split 3.4.0 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/dependabot.yml +7 -0
  4. data/.github/workflows/ci.yml +71 -0
  5. data/.rubocop.yml +177 -1
  6. data/.rubocop_todo.yml +40 -493
  7. data/CHANGELOG.md +41 -0
  8. data/Gemfile +1 -0
  9. data/README.md +26 -6
  10. data/Rakefile +1 -0
  11. data/gemfiles/{4.2.gemfile → 6.1.gemfile} +1 -1
  12. data/gemfiles/7.0.gemfile +9 -0
  13. data/lib/split/algorithms/block_randomization.rb +1 -0
  14. data/lib/split/algorithms/weighted_sample.rb +2 -1
  15. data/lib/split/algorithms/whiplash.rb +3 -2
  16. data/lib/split/alternative.rb +1 -0
  17. data/lib/split/cache.rb +28 -0
  18. data/lib/split/combined_experiments_helper.rb +1 -0
  19. data/lib/split/configuration.rb +8 -12
  20. data/lib/split/dashboard/helpers.rb +1 -0
  21. data/lib/split/dashboard/pagination_helpers.rb +1 -0
  22. data/lib/split/dashboard/paginator.rb +1 -0
  23. data/lib/split/dashboard/public/dashboard.js +10 -0
  24. data/lib/split/dashboard/public/style.css +5 -0
  25. data/lib/split/dashboard/views/_controls.erb +13 -0
  26. data/lib/split/dashboard.rb +17 -2
  27. data/lib/split/encapsulated_helper.rb +3 -2
  28. data/lib/split/engine.rb +5 -4
  29. data/lib/split/exceptions.rb +1 -0
  30. data/lib/split/experiment.rb +82 -60
  31. data/lib/split/experiment_catalog.rb +1 -3
  32. data/lib/split/extensions/string.rb +1 -0
  33. data/lib/split/goals_collection.rb +1 -0
  34. data/lib/split/helper.rb +26 -7
  35. data/lib/split/metric.rb +2 -1
  36. data/lib/split/persistence/cookie_adapter.rb +6 -1
  37. data/lib/split/persistence/redis_adapter.rb +5 -0
  38. data/lib/split/persistence/session_adapter.rb +1 -0
  39. data/lib/split/persistence.rb +4 -2
  40. data/lib/split/redis_interface.rb +8 -28
  41. data/lib/split/trial.rb +20 -10
  42. data/lib/split/user.rb +15 -3
  43. data/lib/split/version.rb +2 -4
  44. data/lib/split/zscore.rb +1 -0
  45. data/lib/split.rb +9 -3
  46. data/spec/alternative_spec.rb +1 -1
  47. data/spec/cache_spec.rb +88 -0
  48. data/spec/configuration_spec.rb +17 -15
  49. data/spec/dashboard_spec.rb +45 -5
  50. data/spec/encapsulated_helper_spec.rb +1 -1
  51. data/spec/experiment_spec.rb +78 -13
  52. data/spec/goals_collection_spec.rb +1 -1
  53. data/spec/helper_spec.rb +68 -32
  54. data/spec/persistence/cookie_adapter_spec.rb +1 -1
  55. data/spec/persistence/redis_adapter_spec.rb +9 -0
  56. data/spec/redis_interface_spec.rb +0 -69
  57. data/spec/spec_helper.rb +5 -6
  58. data/spec/trial_spec.rb +45 -19
  59. data/spec/user_spec.rb +34 -3
  60. data/split.gemspec +7 -7
  61. metadata +27 -35
  62. data/.travis.yml +0 -60
data/lib/split.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'redis'
3
4
 
4
5
  require 'split/algorithms/block_randomization'
5
6
  require 'split/algorithms/weighted_sample'
6
7
  require 'split/algorithms/whiplash'
7
8
  require 'split/alternative'
9
+ require 'split/cache'
8
10
  require 'split/configuration'
9
11
  require 'split/encapsulated_helper'
10
12
  require 'split/exceptions'
@@ -35,9 +37,9 @@ module Split
35
37
  # `Redis::DistRedis`, or `Redis::Namespace`.
36
38
  def redis=(server)
37
39
  @redis = if server.is_a?(String)
38
- Redis.new(:url => server, :thread_safe => true)
40
+ Redis.new(url: server)
39
41
  elsif server.is_a?(Hash)
40
- Redis.new(server.merge(:thread_safe => true))
42
+ Redis.new(server)
41
43
  elsif server.respond_to?(:smembers)
42
44
  server
43
45
  else
@@ -64,11 +66,15 @@ module Split
64
66
  self.configuration ||= Configuration.new
65
67
  yield(configuration)
66
68
  end
69
+
70
+ def cache(namespace, key, &block)
71
+ Split::Cache.fetch(namespace, key, &block)
72
+ end
67
73
  end
68
74
 
69
75
  # Check to see if being run in a Rails application. If so, wait until before_initialize to run configuration so Gems that create ENV variables have the chance to initialize first.
70
76
  if defined?(::Rails)
71
- class Railtie < Rails::Railtie
77
+ class Split::Railtie < Rails::Railtie
72
78
  config.before_initialize { Split.configure {} }
73
79
  end
74
80
  else
@@ -126,7 +126,7 @@ describe Split::Alternative do
126
126
 
127
127
  it "should save to redis" do
128
128
  alternative.save
129
- expect(Split.redis.exists('basket_text:Basket')).to be true
129
+ expect(Split.redis.exists?('basket_text:Basket')).to be true
130
130
  end
131
131
 
132
132
  it "should increment participation count" do
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+
4
+ describe Split::Cache do
5
+
6
+ let(:namespace) { :test_namespace }
7
+ let(:key) { :test_key }
8
+ let(:now) { 1606189017 }
9
+
10
+ before { allow(Time).to receive(:now).and_return(now) }
11
+
12
+ describe 'clear' do
13
+
14
+ before { Split.configuration.cache = true }
15
+
16
+ it 'clears the cache' do
17
+ expect(Time).to receive(:now).and_return(now).exactly(2).times
18
+ Split::Cache.fetch(namespace, key) { Time.now }
19
+ Split::Cache.clear
20
+ Split::Cache.fetch(namespace, key) { Time.now }
21
+ end
22
+ end
23
+
24
+ describe 'clear_key' do
25
+ before { Split.configuration.cache = true }
26
+
27
+ it 'clears the cache' do
28
+ expect(Time).to receive(:now).and_return(now).exactly(3).times
29
+ Split::Cache.fetch(namespace, :key1) { Time.now }
30
+ Split::Cache.fetch(namespace, :key2) { Time.now }
31
+ Split::Cache.clear_key(:key1)
32
+
33
+ Split::Cache.fetch(namespace, :key1) { Time.now }
34
+ Split::Cache.fetch(namespace, :key2) { Time.now }
35
+ end
36
+ end
37
+
38
+ describe 'fetch' do
39
+
40
+ subject { Split::Cache.fetch(namespace, key) { Time.now } }
41
+
42
+ context 'when cache disabled' do
43
+
44
+ before { Split.configuration.cache = false }
45
+
46
+ it 'returns the yield' do
47
+ expect(subject).to eql(now)
48
+ end
49
+
50
+ it 'yields every time' do
51
+ expect(Time).to receive(:now).and_return(now).exactly(2).times
52
+ Split::Cache.fetch(namespace, key) { Time.now }
53
+ Split::Cache.fetch(namespace, key) { Time.now }
54
+ end
55
+ end
56
+
57
+ context 'when cache enabled' do
58
+
59
+ before { Split.configuration.cache = true }
60
+
61
+ it 'returns the yield' do
62
+ expect(subject).to eql(now)
63
+ end
64
+
65
+ it 'yields once' do
66
+ expect(Time).to receive(:now).and_return(now).once
67
+ Split::Cache.fetch(namespace, key) { Time.now }
68
+ Split::Cache.fetch(namespace, key) { Time.now }
69
+ end
70
+
71
+ it 'honors namespace' do
72
+ expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
73
+ expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
74
+
75
+ expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
76
+ expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
77
+ end
78
+
79
+ it 'honors key' do
80
+ expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
81
+ expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
82
+
83
+ expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
84
+ expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -212,23 +212,12 @@ describe Split::Configuration do
212
212
  expect(@config.normalized_experiments).to eq({:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}})
213
213
  end
214
214
 
215
- context 'redis_url configuration [DEPRECATED]' do
216
- it 'should warn on set and assign to #redis' do
217
- expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
218
- @config.redis_url = 'example_url'
219
- expect(@config.redis).to eq('example_url')
220
- end
221
-
222
- it 'should warn on get and return #redis' do
223
- expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
224
- @config.redis = 'example_url'
225
- expect(@config.redis_url).to eq('example_url')
226
- end
227
- end
228
-
229
215
  context "redis configuration" do
230
216
  it "should default to local redis server" do
231
- expect(@config.redis).to eq("redis://localhost:6379")
217
+ old_redis_url = ENV['REDIS_URL']
218
+ ENV.delete('REDIS_URL')
219
+ expect(Split::Configuration.new.redis).to eq("redis://localhost:6379")
220
+ ENV['REDIS_URL'] = old_redis_url
232
221
  end
233
222
 
234
223
  it "should allow for redis url to be configured" do
@@ -238,8 +227,10 @@ describe Split::Configuration do
238
227
 
239
228
  context "provided REDIS_URL environment variable" do
240
229
  it "should use the ENV variable" do
230
+ old_redis_url = ENV['REDIS_URL']
241
231
  ENV['REDIS_URL'] = "env_redis_url"
242
232
  expect(Split::Configuration.new.redis).to eq("env_redis_url")
233
+ ENV['REDIS_URL'] = old_redis_url
243
234
  end
244
235
  end
245
236
  end
@@ -255,4 +246,15 @@ describe Split::Configuration do
255
246
  end
256
247
  end
257
248
 
249
+ context "persistence cookie domain" do
250
+ it "should default to nil" do
251
+ expect(@config.persistence_cookie_domain).to eq(nil)
252
+ end
253
+
254
+ it "should allow the persistence cookie domain to be configured" do
255
+ @config.persistence_cookie_domain = '.acme.com'
256
+ expect(@config.persistence_cookie_domain).to eq('.acme.com')
257
+ end
258
+ end
259
+
258
260
  end
@@ -6,8 +6,16 @@ require 'split/dashboard'
6
6
  describe Split::Dashboard do
7
7
  include Rack::Test::Methods
8
8
 
9
+ class TestDashboard < Split::Dashboard
10
+ include Split::Helper
11
+
12
+ get '/my_experiment' do
13
+ ab_test(params[:experiment], 'blue', 'red')
14
+ end
15
+ end
16
+
9
17
  def app
10
- @app ||= Split::Dashboard
18
+ @app ||= TestDashboard
11
19
  end
12
20
 
13
21
  def link(color)
@@ -90,8 +98,17 @@ describe Split::Dashboard do
90
98
  it "should set current user's alternative" do
91
99
  blue_link.participant_count = 7
92
100
  post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
93
- expect(user[experiment.key]).to eq("blue")
94
- expect(blue_link.participant_count).to eq(8)
101
+
102
+ get "/my_experiment?experiment=#{experiment.name}"
103
+ expect(last_response.body).to include("blue")
104
+ end
105
+
106
+ it "should not modify an existing user" do
107
+ blue_link.participant_count = 7
108
+ post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
109
+
110
+ expect(user[experiment.key]).to eq("red")
111
+ expect(blue_link.participant_count).to eq(7)
95
112
  end
96
113
  end
97
114
 
@@ -108,8 +125,9 @@ describe Split::Dashboard do
108
125
  it "should set current user's alternative" do
109
126
  blue_link.participant_count = 7
110
127
  post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
111
- expect(user[experiment.key]).to eq("blue")
112
- expect(blue_link.participant_count).to eq(8)
128
+
129
+ get "/my_experiment?experiment=#{experiment.name}"
130
+ expect(last_response.body).to include("blue")
113
131
  end
114
132
  end
115
133
  end
@@ -161,6 +179,28 @@ describe Split::Dashboard do
161
179
  end
162
180
  end
163
181
 
182
+ describe "update cohorting" do
183
+ it "calls enable of cohorting when action is enable" do
184
+ post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "enable" }
185
+
186
+ expect(experiment.cohorting_disabled?).to eq false
187
+ end
188
+
189
+ it "calls disable of cohorting when action is disable" do
190
+ post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" }
191
+
192
+ expect(experiment.cohorting_disabled?).to eq true
193
+ end
194
+
195
+ it "calls neither enable or disable cohorting when passed invalid action" do
196
+ previous_value = experiment.cohorting_disabled?
197
+
198
+ post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "other" }
199
+
200
+ expect(experiment.cohorting_disabled?).to eq previous_value
201
+ end
202
+ end
203
+
164
204
  it "should reset an experiment" do
165
205
  red_link.participant_count = 5
166
206
  blue_link.participant_count = 7
@@ -21,7 +21,7 @@ describe Split::EncapsulatedHelper do
21
21
  end
22
22
 
23
23
  it "calls the block with selected alternative" do
24
- expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', nil)
24
+ expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', {})
25
25
  end
26
26
 
27
27
  context "inside a view" do
@@ -35,15 +35,9 @@ describe Split::Experiment do
35
35
  expect(experiment.resettable).to be_truthy
36
36
  end
37
37
 
38
- it "should be resettable when loading from configuration" do
39
- allow(Split.configuration).to receive(:experiment_for).with('some_experiment') { { alternatives: %w(a b) } }
40
-
41
- expect(Split::Experiment.new('some_experiment')).to be_resettable
42
- end
43
-
44
38
  it "should save to redis" do
45
39
  experiment.save
46
- expect(Split.redis.exists('basket_text')).to be true
40
+ expect(Split.redis.exists?('basket_text')).to be true
47
41
  end
48
42
 
49
43
  it "should save the start time to redis" do
@@ -91,7 +85,7 @@ describe Split::Experiment do
91
85
  it "should not create duplicates when saving multiple times" do
92
86
  experiment.save
93
87
  experiment.save
94
- expect(Split.redis.exists('basket_text')).to be true
88
+ expect(Split.redis.exists?('basket_text')).to be true
95
89
  expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}'])
96
90
  end
97
91
 
@@ -124,6 +118,23 @@ describe Split::Experiment do
124
118
  experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :resettable => false)
125
119
  expect(experiment.resettable).to be_falsey
126
120
  end
121
+
122
+ context 'from configuration' do
123
+ let(:experiment_name) { :my_experiment }
124
+ let(:experiments) do
125
+ {
126
+ experiment_name => {
127
+ :alternatives => ['Control Opt', 'Alt one']
128
+ }
129
+ }
130
+ end
131
+
132
+ before { Split.configuration.experiments = experiments }
133
+
134
+ it 'assigns default values to the experiment' do
135
+ expect(Split::Experiment.new(experiment_name).resettable).to eq(true)
136
+ end
137
+ end
127
138
  end
128
139
 
129
140
  describe 'persistent configuration' do
@@ -140,10 +151,23 @@ describe Split::Experiment do
140
151
 
141
152
  describe '#metadata' do
142
153
  let(:experiment) { Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash, :metadata => meta) }
154
+ let(:meta) { { a: 'b' }}
155
+
156
+ before do
157
+ experiment.save
158
+ end
159
+
160
+ it "should delete the key when metadata is removed" do
161
+ experiment.metadata = nil
162
+ experiment.save
163
+
164
+ expect(Split.redis.exists?(experiment.metadata_key)).to be_falsey
165
+ end
166
+
143
167
  context 'simple hash' do
144
168
  let(:meta) { { 'basket' => 'a', 'cart' => 'b' } }
169
+
145
170
  it "should persist metadata in redis" do
146
- experiment.save
147
171
  e = Split::ExperimentCatalog.find('basket_text')
148
172
  expect(e).to eq(experiment)
149
173
  expect(e.metadata).to eq(meta)
@@ -153,7 +177,6 @@ describe Split::Experiment do
153
177
  context 'nested hash' do
154
178
  let(:meta) { { 'basket' => { 'one' => 'two' }, 'cart' => 'b' } }
155
179
  it "should persist metadata in redis" do
156
- experiment.save
157
180
  e = Split::ExperimentCatalog.find('basket_text')
158
181
  expect(e).to eq(experiment)
159
182
  expect(e.metadata).to eq(meta)
@@ -186,7 +209,7 @@ describe Split::Experiment do
186
209
  experiment.save
187
210
 
188
211
  experiment.delete
189
- expect(Split.redis.exists('link_color')).to be false
212
+ expect(Split.redis.exists?('link_color')).to be false
190
213
  expect(Split::ExperimentCatalog.find('link_color')).to be_nil
191
214
  end
192
215
 
@@ -212,8 +235,14 @@ describe Split::Experiment do
212
235
  experiment.delete
213
236
  expect(experiment.start_time).to be_nil
214
237
  end
215
- end
216
238
 
239
+ it "should default cohorting back to false" do
240
+ experiment.disable_cohorting
241
+ expect(experiment.cohorting_disabled?).to eq(true)
242
+ experiment.delete
243
+ expect(experiment.cohorting_disabled?).to eq(false)
244
+ end
245
+ end
217
246
 
218
247
  describe 'winner' do
219
248
  it "should have no winner initially" do
@@ -222,12 +251,17 @@ describe Split::Experiment do
222
251
  end
223
252
 
224
253
  describe 'winner=' do
225
- it "should allow you to specify a winner" do
254
+ it 'should allow you to specify a winner' do
226
255
  experiment.save
227
256
  experiment.winner = 'red'
228
257
  expect(experiment.winner.name).to eq('red')
229
258
  end
230
259
 
260
+ it 'should call the on_experiment_winner_choose hook' do
261
+ expect(Split.configuration.on_experiment_winner_choose).to receive(:call)
262
+ experiment.winner = 'green'
263
+ end
264
+
231
265
  context 'when has_winner state is memoized' do
232
266
  before { expect(experiment).to_not have_winner }
233
267
 
@@ -376,6 +410,22 @@ describe Split::Experiment do
376
410
  end
377
411
  end
378
412
 
413
+ describe "#cohorting_disabled?" do
414
+ it "returns false when nothing has been configured" do
415
+ expect(experiment.cohorting_disabled?).to eq false
416
+ end
417
+
418
+ it "returns true when enable_cohorting is performed" do
419
+ experiment.enable_cohorting
420
+ expect(experiment.cohorting_disabled?).to eq false
421
+ end
422
+
423
+ it "returns false when nothing has been configured" do
424
+ experiment.disable_cohorting
425
+ expect(experiment.cohorting_disabled?).to eq true
426
+ end
427
+ end
428
+
379
429
  describe 'changing an existing experiment' do
380
430
  def same_but_different_alternative
381
431
  Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'yellow', 'orange')
@@ -398,6 +448,21 @@ describe Split::Experiment do
398
448
  expect(same_experiment_again.version).to eq(1)
399
449
  end
400
450
 
451
+ context "when metadata is changed" do
452
+ it "should increase version" do
453
+ experiment.save
454
+ experiment.metadata = { 'foo' => 'bar' }
455
+
456
+ expect { experiment.save }.to change { experiment.version }.by(1)
457
+ end
458
+
459
+ it "does not increase version" do
460
+ experiment.metadata = nil
461
+ experiment.save
462
+ expect { experiment.save }.to change { experiment.version }.by(0)
463
+ end
464
+ end
465
+
401
466
  context 'when experiment configuration is changed' do
402
467
  let(:reset_manually) { false }
403
468
 
@@ -48,7 +48,7 @@ describe Split::GoalsCollection do
48
48
  goals_collection.save
49
49
 
50
50
  goals_collection.delete
51
- expect(Split.redis.exists(goals_key)).to be false
51
+ expect(Split.redis.exists?(goals_key)).to be false
52
52
  end
53
53
  end
54
54
 
data/spec/helper_spec.rb CHANGED
@@ -229,13 +229,15 @@ describe Split::Helper do
229
229
 
230
230
  context "when user already has experiment" do
231
231
  let(:mock_user){ Split::User.new(self, {'test_0' => 'test-alt'}) }
232
- before{
232
+
233
+ before do
233
234
  Split.configure do |config|
234
235
  config.allow_multiple_experiments = 'control'
235
236
  end
237
+
236
238
  Split::ExperimentCatalog.find_or_initialize('test_0', 'control', 'test-alt').save
237
239
  Split::ExperimentCatalog.find_or_initialize('test_1', 'control', 'test-alt').save
238
- }
240
+ end
239
241
 
240
242
  it "should restore previously selected alternative" do
241
243
  expect(ab_user.active_experiments.size).to eq 1
@@ -243,6 +245,16 @@ describe Split::Helper do
243
245
  expect(ab_test(:test_0, {'control' => 1}, {"test-alt" => 100})).to eq 'test-alt'
244
246
  end
245
247
 
248
+ it "should select the correct alternatives after experiment resets" do
249
+ experiment = Split::ExperimentCatalog.find(:test_0)
250
+ experiment.reset
251
+ mock_user[experiment.key] = 'test-alt'
252
+
253
+ expect(ab_user.active_experiments.size).to eq 1
254
+ expect(ab_test(:test_0, {'control' => 100}, {"test-alt" => 1})).to eq 'test-alt'
255
+ expect(ab_test(:test_0, {'control' => 0}, {"test-alt" => 100})).to eq 'test-alt'
256
+ end
257
+
246
258
  it "lets override existing choice" do
247
259
  pending "this requires user store reset on first call not depending on whelther it is current trial"
248
260
  @params = { 'ab_test' => { 'test_1' => 'test-alt' } }
@@ -265,33 +277,63 @@ describe Split::Helper do
265
277
  end
266
278
 
267
279
  describe 'metadata' do
268
- before do
269
- Split.configuration.experiments = {
270
- :my_experiment => {
271
- :alternatives => ["one", "two"],
272
- :resettable => false,
273
- :metadata => { 'one' => 'Meta1', 'two' => 'Meta2' }
280
+ context 'is defined' do
281
+ before do
282
+ Split.configuration.experiments = {
283
+ :my_experiment => {
284
+ :alternatives => ["one", "two"],
285
+ :resettable => false,
286
+ :metadata => { 'one' => 'Meta1', 'two' => 'Meta2' }
287
+ }
274
288
  }
275
- }
276
- end
289
+ end
277
290
 
278
- it 'should be passed to helper block' do
279
- @params = { 'ab_test' => { 'my_experiment' => 'one' } }
280
- expect(ab_test('my_experiment')).to eq 'one'
281
- expect(ab_test('my_experiment') do |alternative, meta|
282
- meta
283
- end).to eq('Meta1')
291
+ it 'should be passed to helper block' do
292
+ @params = { 'ab_test' => { 'my_experiment' => 'two' } }
293
+ expect(ab_test('my_experiment')).to eq 'two'
294
+ expect(ab_test('my_experiment') do |alternative, meta|
295
+ meta
296
+ end).to eq('Meta2')
297
+ end
298
+
299
+ it 'should pass control metadata helper block if library disabled' do
300
+ Split.configure do |config|
301
+ config.enabled = false
302
+ end
303
+
304
+ expect(ab_test('my_experiment')).to eq 'one'
305
+ expect(ab_test('my_experiment') do |_, meta|
306
+ meta
307
+ end).to eq('Meta1')
308
+ end
284
309
  end
285
310
 
286
- it 'should pass empty hash to helper block if library disabled' do
287
- Split.configure do |config|
288
- config.enabled = false
311
+ context 'is not defined' do
312
+ before do
313
+ Split.configuration.experiments = {
314
+ :my_experiment => {
315
+ :alternatives => ["one", "two"],
316
+ :resettable => false,
317
+ :metadata => nil
318
+ }
319
+ }
320
+ end
321
+
322
+ it 'should be passed to helper block' do
323
+ expect(ab_test('my_experiment') do |alternative, meta|
324
+ meta
325
+ end).to eq({})
289
326
  end
290
327
 
291
- expect(ab_test('my_experiment')).to eq 'one'
292
- expect(ab_test('my_experiment') do |_, meta|
293
- meta
294
- end).to eq({})
328
+ it 'should pass control metadata helper block if library disabled' do
329
+ Split.configure do |config|
330
+ config.enabled = false
331
+ end
332
+
333
+ expect(ab_test('my_experiment') do |_, meta|
334
+ meta
335
+ end).to eq({})
336
+ end
295
337
  end
296
338
  end
297
339
 
@@ -1096,15 +1138,9 @@ describe Split::Helper do
1096
1138
  end
1097
1139
 
1098
1140
  it "should increment the counter for the specified-goal completed alternative" do
1099
- expect(lambda {
1100
- expect(lambda {
1101
- ab_finished({"link_color" => ["purchase"]})
1102
- }).not_to change {
1103
- Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
1104
- }
1105
- }).to change {
1106
- Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
1107
- }.by(1)
1141
+ expect{ ab_finished({"link_color" => ["purchase"]}) }
1142
+ .to change{ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2) }.by(0)
1143
+ .and change{ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1) }.by(1)
1108
1144
  end
1109
1145
  end
1110
1146
  end
@@ -52,7 +52,7 @@ describe Split::Persistence::CookieAdapter do
52
52
  it "puts multiple experiments in a single cookie" do
53
53
  subject["foo"] = "FOO"
54
54
  subject["bar"] = "BAR"
55
- expect(context.response.headers["Set-Cookie"]).to match(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} -0000\Z/)
55
+ expect(context.response.headers["Set-Cookie"]).to match(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} [A-Z]{3}\Z/)
56
56
  end
57
57
 
58
58
  it "ensure other added cookies are not overriden" do
@@ -60,6 +60,15 @@ describe Split::Persistence::RedisAdapter do
60
60
  end
61
61
  end
62
62
 
63
+ describe '#find' do
64
+ before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'frag'}, :namespace => 'a_namespace') }
65
+
66
+ it "should create and user from a given key" do
67
+ adapter = Split::Persistence::RedisAdapter.find(2)
68
+ expect(adapter.redis_key).to eq("a_namespace:2")
69
+ end
70
+ end
71
+
63
72
  context 'functional tests' do
64
73
  before { Split::Persistence::RedisAdapter.with_config(:lookup_by => 'lookup') }
65
74