ab-split 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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