ab-split 1.0.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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +30 -0
  3. data/.csslintrc +2 -0
  4. data/.eslintignore +1 -0
  5. data/.eslintrc +213 -0
  6. data/.github/FUNDING.yml +1 -0
  7. data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  8. data/.rspec +1 -0
  9. data/.rubocop.yml +7 -0
  10. data/.rubocop_todo.yml +679 -0
  11. data/.travis.yml +60 -0
  12. data/Appraisals +19 -0
  13. data/CHANGELOG.md +696 -0
  14. data/CODE_OF_CONDUCT.md +74 -0
  15. data/CONTRIBUTING.md +62 -0
  16. data/Gemfile +7 -0
  17. data/LICENSE +22 -0
  18. data/README.md +955 -0
  19. data/Rakefile +9 -0
  20. data/ab-split.gemspec +44 -0
  21. data/gemfiles/4.2.gemfile +9 -0
  22. data/gemfiles/5.0.gemfile +9 -0
  23. data/gemfiles/5.1.gemfile +9 -0
  24. data/gemfiles/5.2.gemfile +9 -0
  25. data/gemfiles/6.0.gemfile +9 -0
  26. data/lib/split.rb +76 -0
  27. data/lib/split/algorithms/block_randomization.rb +23 -0
  28. data/lib/split/algorithms/weighted_sample.rb +18 -0
  29. data/lib/split/algorithms/whiplash.rb +38 -0
  30. data/lib/split/alternative.rb +191 -0
  31. data/lib/split/combined_experiments_helper.rb +37 -0
  32. data/lib/split/configuration.rb +255 -0
  33. data/lib/split/dashboard.rb +74 -0
  34. data/lib/split/dashboard/helpers.rb +45 -0
  35. data/lib/split/dashboard/pagination_helpers.rb +86 -0
  36. data/lib/split/dashboard/paginator.rb +16 -0
  37. data/lib/split/dashboard/public/dashboard-filtering.js +43 -0
  38. data/lib/split/dashboard/public/dashboard.js +24 -0
  39. data/lib/split/dashboard/public/jquery-1.11.1.min.js +4 -0
  40. data/lib/split/dashboard/public/reset.css +48 -0
  41. data/lib/split/dashboard/public/style.css +328 -0
  42. data/lib/split/dashboard/views/_controls.erb +18 -0
  43. data/lib/split/dashboard/views/_experiment.erb +155 -0
  44. data/lib/split/dashboard/views/_experiment_with_goal_header.erb +8 -0
  45. data/lib/split/dashboard/views/index.erb +26 -0
  46. data/lib/split/dashboard/views/layout.erb +27 -0
  47. data/lib/split/encapsulated_helper.rb +42 -0
  48. data/lib/split/engine.rb +15 -0
  49. data/lib/split/exceptions.rb +6 -0
  50. data/lib/split/experiment.rb +486 -0
  51. data/lib/split/experiment_catalog.rb +51 -0
  52. data/lib/split/extensions/string.rb +16 -0
  53. data/lib/split/goals_collection.rb +45 -0
  54. data/lib/split/helper.rb +165 -0
  55. data/lib/split/metric.rb +101 -0
  56. data/lib/split/persistence.rb +28 -0
  57. data/lib/split/persistence/cookie_adapter.rb +94 -0
  58. data/lib/split/persistence/dual_adapter.rb +85 -0
  59. data/lib/split/persistence/redis_adapter.rb +57 -0
  60. data/lib/split/persistence/session_adapter.rb +29 -0
  61. data/lib/split/redis_interface.rb +50 -0
  62. data/lib/split/trial.rb +117 -0
  63. data/lib/split/user.rb +69 -0
  64. data/lib/split/version.rb +7 -0
  65. data/lib/split/zscore.rb +57 -0
  66. data/spec/algorithms/block_randomization_spec.rb +32 -0
  67. data/spec/algorithms/weighted_sample_spec.rb +19 -0
  68. data/spec/algorithms/whiplash_spec.rb +24 -0
  69. data/spec/alternative_spec.rb +320 -0
  70. data/spec/combined_experiments_helper_spec.rb +57 -0
  71. data/spec/configuration_spec.rb +258 -0
  72. data/spec/dashboard/pagination_helpers_spec.rb +200 -0
  73. data/spec/dashboard/paginator_spec.rb +37 -0
  74. data/spec/dashboard_helpers_spec.rb +42 -0
  75. data/spec/dashboard_spec.rb +210 -0
  76. data/spec/encapsulated_helper_spec.rb +52 -0
  77. data/spec/experiment_catalog_spec.rb +53 -0
  78. data/spec/experiment_spec.rb +533 -0
  79. data/spec/goals_collection_spec.rb +80 -0
  80. data/spec/helper_spec.rb +1111 -0
  81. data/spec/metric_spec.rb +31 -0
  82. data/spec/persistence/cookie_adapter_spec.rb +106 -0
  83. data/spec/persistence/dual_adapter_spec.rb +194 -0
  84. data/spec/persistence/redis_adapter_spec.rb +90 -0
  85. data/spec/persistence/session_adapter_spec.rb +32 -0
  86. data/spec/persistence_spec.rb +34 -0
  87. data/spec/redis_interface_spec.rb +111 -0
  88. data/spec/spec_helper.rb +52 -0
  89. data/spec/split_spec.rb +43 -0
  90. data/spec/support/cookies_mock.rb +20 -0
  91. data/spec/trial_spec.rb +299 -0
  92. data/spec/user_spec.rb +87 -0
  93. metadata +322 -0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe Split::Persistence do
5
+
6
+ subject { Split::Persistence }
7
+
8
+ describe ".adapter" do
9
+ context "when the persistence config is a symbol" do
10
+ it "should return the appropriate adapter for the symbol" do
11
+ expect(Split.configuration).to receive(:persistence).twice.and_return(:cookie)
12
+ expect(subject.adapter).to eq(Split::Persistence::CookieAdapter)
13
+ end
14
+
15
+ it "should return an adapter whose class is present in Split::Persistence::ADAPTERS" do
16
+ expect(Split.configuration).to receive(:persistence).twice.and_return(:cookie)
17
+ expect(Split::Persistence::ADAPTERS.values).to include(subject.adapter)
18
+ end
19
+
20
+ it "should raise if the adapter cannot be found" do
21
+ expect(Split.configuration).to receive(:persistence).twice.and_return(:something_weird)
22
+ expect { subject.adapter }.to raise_error(Split::InvalidPersistenceAdapterError)
23
+ end
24
+ end
25
+ context "when the persistence config is a class" do
26
+ let(:custom_adapter_class) { MyCustomAdapterClass = Class.new }
27
+ it "should return that class" do
28
+ expect(Split.configuration).to receive(:persistence).twice.and_return(custom_adapter_class)
29
+ expect(subject.adapter).to eq(MyCustomAdapterClass)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe Split::RedisInterface do
4
+ let(:list_name) { 'list_name' }
5
+ let(:set_name) { 'set_name' }
6
+ let(:interface) { described_class.new }
7
+
8
+ describe '#persist_list' do
9
+ subject(:persist_list) do
10
+ interface.persist_list(list_name, %w(a b c d))
11
+ end
12
+
13
+ specify do
14
+ expect(persist_list).to eq %w(a b c d)
15
+ expect(Split.redis.lindex(list_name, 0)).to eq 'a'
16
+ expect(Split.redis.lindex(list_name, 1)).to eq 'b'
17
+ expect(Split.redis.lindex(list_name, 2)).to eq 'c'
18
+ expect(Split.redis.lindex(list_name, 3)).to eq 'd'
19
+ expect(Split.redis.llen(list_name)).to eq 4
20
+ end
21
+
22
+ context 'list is overwritten but not deleted' do
23
+ specify do
24
+ expect(persist_list).to eq %w(a b c d)
25
+ interface.persist_list(list_name, ['z'])
26
+ expect(Split.redis.lindex(list_name, 0)).to eq 'z'
27
+ expect(Split.redis.llen(list_name)).to eq 1
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '#add_to_list' do
33
+ subject(:add_to_list) do
34
+ interface.add_to_list(list_name, 'y')
35
+ interface.add_to_list(list_name, 'z')
36
+ end
37
+
38
+ specify do
39
+ add_to_list
40
+ expect(Split.redis.lindex(list_name, 0)).to eq 'y'
41
+ expect(Split.redis.lindex(list_name, 1)).to eq 'z'
42
+ expect(Split.redis.llen(list_name)).to eq 2
43
+ end
44
+ end
45
+
46
+ describe '#set_list_index' do
47
+ subject(:set_list_index) do
48
+ interface.add_to_list(list_name, 'y')
49
+ interface.add_to_list(list_name, 'z')
50
+ interface.set_list_index(list_name, 0, 'a')
51
+ end
52
+
53
+ specify do
54
+ set_list_index
55
+ expect(Split.redis.lindex(list_name, 0)).to eq 'a'
56
+ expect(Split.redis.lindex(list_name, 1)).to eq 'z'
57
+ expect(Split.redis.llen(list_name)).to eq 2
58
+ end
59
+ end
60
+
61
+ describe '#list_length' do
62
+ subject(:list_length) do
63
+ interface.add_to_list(list_name, 'y')
64
+ interface.add_to_list(list_name, 'z')
65
+ interface.list_length(list_name)
66
+ end
67
+
68
+ specify do
69
+ expect(list_length).to eq 2
70
+ end
71
+ end
72
+
73
+ describe '#remove_last_item_from_list' do
74
+ subject(:remove_last_item_from_list) do
75
+ interface.add_to_list(list_name, 'y')
76
+ interface.add_to_list(list_name, 'z')
77
+ interface.remove_last_item_from_list(list_name)
78
+ end
79
+
80
+ specify do
81
+ remove_last_item_from_list
82
+ expect(Split.redis.lindex(list_name, 0)).to eq 'y'
83
+ expect(Split.redis.llen(list_name)).to eq 1
84
+ end
85
+ end
86
+
87
+ describe '#make_list_length' do
88
+ subject(:make_list_length) do
89
+ interface.add_to_list(list_name, 'y')
90
+ interface.add_to_list(list_name, 'z')
91
+ interface.make_list_length(list_name, 1)
92
+ end
93
+
94
+ specify do
95
+ make_list_length
96
+ expect(Split.redis.lindex(list_name, 0)).to eq 'y'
97
+ expect(Split.redis.llen(list_name)).to eq 1
98
+ end
99
+ end
100
+
101
+ describe '#add_to_set' do
102
+ subject(:add_to_set) do
103
+ interface.add_to_set(set_name, 'something')
104
+ end
105
+
106
+ specify do
107
+ add_to_set
108
+ expect(Split.redis.sismember(set_name, 'something')).to be true
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ ENV['RACK_ENV'] = "test"
3
+
4
+ require 'rubygems'
5
+ require 'bundler/setup'
6
+
7
+ require 'simplecov'
8
+ SimpleCov.start
9
+
10
+ require 'split'
11
+ require 'ostruct'
12
+ require 'yaml'
13
+
14
+ Dir['./spec/support/*.rb'].each { |f| require f }
15
+
16
+ require "fakeredis"
17
+
18
+ G_fakeredis = Redis.new
19
+
20
+ module GlobalSharedContext
21
+ extend RSpec::SharedContext
22
+ let(:mock_user){ Split::User.new(double(session: {})) }
23
+ before(:each) do
24
+ Split.configuration = Split::Configuration.new
25
+ Split.redis = G_fakeredis
26
+ Split.redis.flushall
27
+ @ab_user = mock_user
28
+ params = nil
29
+ end
30
+ end
31
+
32
+ RSpec.configure do |config|
33
+ config.order = 'random'
34
+ config.include GlobalSharedContext
35
+ end
36
+
37
+ def session
38
+ @session ||= {}
39
+ end
40
+
41
+ def params
42
+ @params ||= {}
43
+ end
44
+
45
+ def request(ua = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27')
46
+ @request ||= begin
47
+ r = OpenStruct.new
48
+ r.user_agent = ua
49
+ r.ip = '192.168.1.1'
50
+ r
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+
4
+ RSpec.describe Split do
5
+
6
+ around(:each) do |ex|
7
+ old_env, old_redis = [ENV.delete('REDIS_URL'), Split.redis]
8
+ ex.run
9
+ ENV['REDIS_URL'] = old_env
10
+ Split.redis = old_redis
11
+ end
12
+
13
+ describe '#redis=' do
14
+ it 'accepts a url string' do
15
+ Split.redis = 'redis://localhost:6379'
16
+ expect(Split.redis).to be_a(Redis)
17
+
18
+ client = Split.redis.connection
19
+ expect(client[:host]).to eq("localhost")
20
+ expect(client[:port]).to eq(6379)
21
+ end
22
+
23
+ it 'accepts an options hash' do
24
+ Split.redis = {host: 'localhost', port: 6379, db: 12}
25
+ expect(Split.redis).to be_a(Redis)
26
+
27
+ client = Split.redis.connection
28
+ expect(client[:host]).to eq("localhost")
29
+ expect(client[:port]).to eq(6379)
30
+ expect(client[:db]).to eq(12)
31
+ end
32
+
33
+ it 'accepts a valid Redis instance' do
34
+ other_redis = Redis.new(url: "redis://localhost:6379")
35
+ Split.redis = other_redis
36
+ expect(Split.redis).to eq(other_redis)
37
+ end
38
+
39
+ it 'raises an ArgumentError when server cannot be determined' do
40
+ expect { Split.redis = Object.new }.to raise_error(ArgumentError)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ class CookiesMock
3
+
4
+ def initialize
5
+ @cookies = {}
6
+ end
7
+
8
+ def []=(key, value)
9
+ @cookies[key] = value[:value]
10
+ end
11
+
12
+ def [](key)
13
+ @cookies[key]
14
+ end
15
+
16
+ def delete(key)
17
+ @cookies.delete(key)
18
+ end
19
+
20
+ end
@@ -0,0 +1,299 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'split/trial'
4
+
5
+ describe Split::Trial do
6
+ let(:user) { mock_user }
7
+ let(:alternatives) { ['basket', 'cart'] }
8
+ let(:experiment) do
9
+ Split::Experiment.new('basket_text', :alternatives => alternatives).save
10
+ end
11
+
12
+ it "should be initializeable" do
13
+ experiment = double('experiment')
14
+ alternative = double('alternative', :kind_of? => Split::Alternative)
15
+ trial = Split::Trial.new(:experiment => experiment, :alternative => alternative)
16
+ expect(trial.experiment).to eq(experiment)
17
+ expect(trial.alternative).to eq(alternative)
18
+ end
19
+
20
+ describe "alternative" do
21
+ it "should use the alternative if specified" do
22
+ alternative = double('alternative', :kind_of? => Split::Alternative)
23
+ trial = Split::Trial.new(:experiment => double('experiment'),
24
+ :alternative => alternative, :user => user)
25
+ expect(trial).not_to receive(:choose)
26
+ expect(trial.alternative).to eq(alternative)
27
+ end
28
+
29
+ it "should load the alternative when the alternative name is set" do
30
+ experiment = Split::Experiment.new('basket_text', :alternatives => ['basket', 'cart'])
31
+ experiment.save
32
+
33
+ trial = Split::Trial.new(:experiment => experiment, :alternative => 'basket')
34
+ expect(trial.alternative.name).to eq('basket')
35
+ end
36
+ end
37
+
38
+ describe "metadata" do
39
+ let(:metadata) { Hash[alternatives.map { |k| [k, "Metadata for #{k}"] }] }
40
+ let(:experiment) do
41
+ Split::Experiment.new('basket_text', :alternatives => alternatives, :metadata => metadata).save
42
+ end
43
+
44
+ it 'has metadata on each trial' do
45
+ trial = Split::Trial.new(:experiment => experiment, :user => user, :metadata => metadata['cart'],
46
+ :override => 'cart')
47
+ expect(trial.metadata).to eq(metadata['cart'])
48
+ end
49
+
50
+ it 'has metadata on each trial from the experiment' do
51
+ trial = Split::Trial.new(:experiment => experiment, :user => user)
52
+ trial.choose!
53
+ expect(trial.metadata).to eq(metadata[trial.alternative.name])
54
+ expect(trial.metadata).to match(/#{trial.alternative.name}/)
55
+ end
56
+ end
57
+
58
+ describe "#choose!" do
59
+ let(:context) { double(on_trial_callback: 'test callback') }
60
+ let(:trial) do
61
+ Split::Trial.new(:user => user, :experiment => experiment)
62
+ end
63
+
64
+ shared_examples_for 'a trial with callbacks' do
65
+ it 'does not run if on_trial callback is not respondable' do
66
+ Split.configuration.on_trial = :foo
67
+ allow(context).to receive(:respond_to?).with(:foo, true).and_return false
68
+ expect(context).to_not receive(:foo)
69
+ trial.choose! context
70
+ end
71
+ it 'runs on_trial callback' do
72
+ Split.configuration.on_trial = :on_trial_callback
73
+ expect(context).to receive(:on_trial_callback)
74
+ trial.choose! context
75
+ end
76
+ it 'does not run nil on_trial callback' do
77
+ Split.configuration.on_trial = nil
78
+ expect(context).not_to receive(:on_trial_callback)
79
+ trial.choose! context
80
+ end
81
+ end
82
+
83
+ def expect_alternative(trial, alternative_name)
84
+ 3.times do
85
+ trial.choose! context
86
+ expect(alternative_name).to include(trial.alternative.name)
87
+ end
88
+ end
89
+
90
+ context "when override is present" do
91
+ let(:override) { 'cart' }
92
+ let(:trial) do
93
+ Split::Trial.new(:user => user, :experiment => experiment, :override => override)
94
+ end
95
+
96
+ it_behaves_like 'a trial with callbacks'
97
+
98
+ it "picks the override" do
99
+ expect(experiment).to_not receive(:next_alternative)
100
+ expect_alternative(trial, override)
101
+ end
102
+
103
+ context "when alternative doesn't exist" do
104
+ let(:override) { nil }
105
+ it 'falls back on next_alternative' do
106
+ expect(experiment).to receive(:next_alternative).and_call_original
107
+ expect_alternative(trial, alternatives)
108
+ end
109
+ end
110
+ end
111
+
112
+ context "when disabled option is true" do
113
+ let(:trial) do
114
+ Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
115
+ end
116
+
117
+ it "picks the control", :aggregate_failures do
118
+ Split.configuration.on_trial = :on_trial_callback
119
+ expect(experiment).to_not receive(:next_alternative)
120
+
121
+ expect(context).not_to receive(:on_trial_callback)
122
+
123
+ expect_alternative(trial, 'basket')
124
+ Split.configuration.on_trial = nil
125
+ end
126
+ end
127
+
128
+ context "when Split is globally disabled" do
129
+ it "picks the control and does not run on_trial callbacks", :aggregate_failures do
130
+ Split.configuration.enabled = false
131
+ Split.configuration.on_trial = :on_trial_callback
132
+
133
+ expect(experiment).to_not receive(:next_alternative)
134
+ expect(context).not_to receive(:on_trial_callback)
135
+ expect_alternative(trial, 'basket')
136
+
137
+ Split.configuration.enabled = true
138
+ Split.configuration.on_trial = nil
139
+ end
140
+ end
141
+
142
+ context "when experiment has winner" do
143
+ let(:trial) do
144
+ Split::Trial.new(:user => user, :experiment => experiment)
145
+ end
146
+
147
+ it_behaves_like 'a trial with callbacks'
148
+
149
+ it "picks the winner" do
150
+ experiment.winner = 'cart'
151
+ expect(experiment).to_not receive(:next_alternative)
152
+
153
+ expect_alternative(trial, 'cart')
154
+ end
155
+ end
156
+
157
+ context "when exclude is true" do
158
+ let(:trial) do
159
+ Split::Trial.new(:user => user, :experiment => experiment, :exclude => true)
160
+ end
161
+
162
+ it_behaves_like 'a trial with callbacks'
163
+
164
+ it "picks the control" do
165
+ expect(experiment).to_not receive(:next_alternative)
166
+ expect_alternative(trial, 'basket')
167
+ end
168
+ end
169
+
170
+ context "when user is already participating" do
171
+ it_behaves_like 'a trial with callbacks'
172
+
173
+ it "picks the same alternative" do
174
+ user[experiment.key] = 'basket'
175
+ expect(experiment).to_not receive(:next_alternative)
176
+
177
+ expect_alternative(trial, 'basket')
178
+ end
179
+
180
+ context "when alternative is not found" do
181
+ it "falls back on next_alternative" do
182
+ user[experiment.key] = 'notfound'
183
+ expect(experiment).to receive(:next_alternative).and_call_original
184
+ expect_alternative(trial, alternatives)
185
+ end
186
+ end
187
+ end
188
+
189
+ context "when user is a new participant" do
190
+ it "picks a new alternative and runs on_trial_choose callback", :aggregate_failures do
191
+ Split.configuration.on_trial_choose = :on_trial_choose_callback
192
+
193
+ expect(experiment).to receive(:next_alternative).and_call_original
194
+ expect(context).to receive(:on_trial_choose_callback)
195
+
196
+ trial.choose! context
197
+
198
+ expect(trial.alternative.name).to_not be_empty
199
+ Split.configuration.on_trial_choose = nil
200
+ end
201
+ end
202
+ end
203
+
204
+ describe "#complete!" do
205
+ let(:trial) { Split::Trial.new(:user => user, :experiment => experiment) }
206
+ context 'when there are no goals' do
207
+ it 'should complete the trial' do
208
+ trial.choose!
209
+ old_completed_count = trial.alternative.completed_count
210
+ trial.complete!
211
+ expect(trial.alternative.completed_count).to be(old_completed_count+1)
212
+ end
213
+ end
214
+
215
+ context 'when there are many goals' do
216
+ let(:goals) { ['first', 'second'] }
217
+ let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goals) }
218
+ shared_examples_for "goal completion" do
219
+ it 'should not complete the trial' do
220
+ trial.choose!
221
+ old_completed_count = trial.alternative.completed_count
222
+ trial.complete!(goal)
223
+ expect(trial.alternative.completed_count).to_not be(old_completed_count+1)
224
+ end
225
+ end
226
+
227
+ describe 'Array of Goals' do
228
+ let(:goal) { [goals.first] }
229
+ it_behaves_like 'goal completion'
230
+ end
231
+
232
+ describe 'String of Goal' do
233
+ let(:goal) { goals.first }
234
+ it_behaves_like 'goal completion'
235
+ end
236
+
237
+ end
238
+ end
239
+
240
+ describe "alternative recording" do
241
+ before(:each) { Split.configuration.store_override = false }
242
+
243
+ context "when override is present" do
244
+ it "stores when store_override is true" do
245
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'basket')
246
+
247
+ Split.configuration.store_override = true
248
+ expect(user).to receive("[]=")
249
+ trial.choose!
250
+ expect(trial.alternative.participant_count).to eq(1)
251
+ end
252
+
253
+ it "does not store when store_override is false" do
254
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'basket')
255
+
256
+ expect(user).to_not receive("[]=")
257
+ trial.choose!
258
+ end
259
+ end
260
+
261
+ context "when disabled is present" do
262
+ it "stores when store_override is true" do
263
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
264
+
265
+ Split.configuration.store_override = true
266
+ expect(user).to receive("[]=")
267
+ trial.choose!
268
+ end
269
+
270
+ it "does not store when store_override is false" do
271
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
272
+
273
+ expect(user).to_not receive("[]=")
274
+ trial.choose!
275
+ end
276
+ end
277
+
278
+ context "when exclude is present" do
279
+ it "does not store" do
280
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :exclude => true)
281
+
282
+ expect(user).to_not receive("[]=")
283
+ trial.choose!
284
+ end
285
+ end
286
+
287
+ context 'when experiment has winner' do
288
+ let(:trial) do
289
+ experiment.winner = 'cart'
290
+ Split::Trial.new(:user => user, :experiment => experiment)
291
+ end
292
+
293
+ it 'does not store' do
294
+ expect(user).to_not receive("[]=")
295
+ trial.choose!
296
+ end
297
+ end
298
+ end
299
+ end