split 3.0.0 → 3.4.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.
- checksums.yaml +5 -5
- data/.eslintrc +1 -1
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.rspec +1 -0
- data/.rubocop.yml +6 -1155
- data/.rubocop_todo.yml +679 -0
- data/.travis.yml +46 -2
- data/Appraisals +12 -1
- data/CHANGELOG.md +116 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/CONTRIBUTING.md +54 -5
- data/Gemfile +1 -0
- data/LICENSE +1 -1
- data/README.md +209 -118
- data/Rakefile +1 -0
- data/gemfiles/4.2.gemfile +1 -1
- data/gemfiles/5.0.gemfile +1 -2
- data/gemfiles/5.1.gemfile +9 -0
- data/gemfiles/5.2.gemfile +9 -0
- data/gemfiles/6.0.gemfile +9 -0
- data/lib/split/algorithms/block_randomization.rb +1 -0
- data/lib/split/alternative.rb +6 -3
- data/lib/split/combined_experiments_helper.rb +37 -0
- data/lib/split/configuration.rb +17 -2
- data/lib/split/dashboard/helpers.rb +2 -2
- data/lib/split/dashboard/pagination_helpers.rb +86 -0
- data/lib/split/dashboard/paginator.rb +16 -0
- data/lib/split/dashboard/public/style.css +9 -0
- data/lib/split/dashboard/views/index.erb +5 -1
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/dashboard.rb +6 -1
- data/lib/split/engine.rb +6 -2
- data/lib/split/experiment.rb +34 -22
- data/lib/split/goals_collection.rb +1 -0
- data/lib/split/helper.rb +17 -3
- data/lib/split/persistence/cookie_adapter.rb +53 -15
- data/lib/split/persistence/dual_adapter.rb +54 -12
- data/lib/split/redis_interface.rb +2 -3
- data/lib/split/trial.rb +4 -6
- data/lib/split/user.rb +5 -1
- data/lib/split/version.rb +1 -1
- data/lib/split.rb +9 -1
- data/spec/alternative_spec.rb +12 -0
- data/spec/combined_experiments_helper_spec.rb +57 -0
- data/spec/dashboard/pagination_helpers_spec.rb +200 -0
- data/spec/dashboard/paginator_spec.rb +37 -0
- data/spec/dashboard_helpers_spec.rb +2 -2
- data/spec/dashboard_spec.rb +37 -16
- data/spec/encapsulated_helper_spec.rb +1 -1
- data/spec/experiment_spec.rb +45 -6
- data/spec/helper_spec.rb +143 -80
- data/spec/persistence/cookie_adapter_spec.rb +90 -23
- data/spec/persistence/dual_adapter_spec.rb +160 -68
- data/spec/split_spec.rb +7 -7
- data/spec/trial_spec.rb +20 -0
- data/spec/user_spec.rb +11 -0
- data/split.gemspec +17 -7
- metadata +53 -19
data/spec/experiment_spec.rb
CHANGED
|
@@ -35,6 +35,12 @@ 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
|
+
|
|
38
44
|
it "should save to redis" do
|
|
39
45
|
experiment.save
|
|
40
46
|
expect(Split.redis.exists('basket_text')).to be true
|
|
@@ -86,7 +92,7 @@ describe Split::Experiment do
|
|
|
86
92
|
experiment.save
|
|
87
93
|
experiment.save
|
|
88
94
|
expect(Split.redis.exists('basket_text')).to be true
|
|
89
|
-
expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['Basket', "Cart"])
|
|
95
|
+
expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}'])
|
|
90
96
|
end
|
|
91
97
|
|
|
92
98
|
describe 'new record?' do
|
|
@@ -213,12 +219,41 @@ describe Split::Experiment do
|
|
|
213
219
|
it "should have no winner initially" do
|
|
214
220
|
expect(experiment.winner).to be_nil
|
|
215
221
|
end
|
|
222
|
+
end
|
|
216
223
|
|
|
224
|
+
describe 'winner=' do
|
|
217
225
|
it "should allow you to specify a winner" do
|
|
218
226
|
experiment.save
|
|
219
227
|
experiment.winner = 'red'
|
|
220
228
|
expect(experiment.winner.name).to eq('red')
|
|
221
229
|
end
|
|
230
|
+
|
|
231
|
+
context 'when has_winner state is memoized' do
|
|
232
|
+
before { expect(experiment).to_not have_winner }
|
|
233
|
+
|
|
234
|
+
it 'should keep has_winner state consistent' do
|
|
235
|
+
experiment.winner = 'red'
|
|
236
|
+
expect(experiment).to have_winner
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
describe 'reset_winner' do
|
|
242
|
+
before { experiment.winner = 'green' }
|
|
243
|
+
|
|
244
|
+
it 'should reset the winner' do
|
|
245
|
+
experiment.reset_winner
|
|
246
|
+
expect(experiment.winner).to be_nil
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
context 'when has_winner state is memoized' do
|
|
250
|
+
before { expect(experiment).to have_winner }
|
|
251
|
+
|
|
252
|
+
it 'should keep has_winner state consistent' do
|
|
253
|
+
experiment.reset_winner
|
|
254
|
+
expect(experiment).to_not have_winner
|
|
255
|
+
end
|
|
256
|
+
end
|
|
222
257
|
end
|
|
223
258
|
|
|
224
259
|
describe 'has_winner?' do
|
|
@@ -235,6 +270,12 @@ describe Split::Experiment do
|
|
|
235
270
|
expect(experiment).to_not have_winner
|
|
236
271
|
end
|
|
237
272
|
end
|
|
273
|
+
|
|
274
|
+
it 'memoizes has_winner state' do
|
|
275
|
+
expect(experiment).to receive(:winner).once
|
|
276
|
+
expect(experiment).to_not have_winner
|
|
277
|
+
expect(experiment).to_not have_winner
|
|
278
|
+
end
|
|
238
279
|
end
|
|
239
280
|
|
|
240
281
|
describe 'reset' do
|
|
@@ -414,9 +455,7 @@ describe Split::Experiment do
|
|
|
414
455
|
}
|
|
415
456
|
|
|
416
457
|
context "saving experiment" do
|
|
417
|
-
|
|
418
|
-
Split::ExperimentCatalog.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red', 'green')
|
|
419
|
-
end
|
|
458
|
+
let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red', 'green') }
|
|
420
459
|
|
|
421
460
|
before { experiment.save }
|
|
422
461
|
|
|
@@ -425,7 +464,7 @@ describe Split::Experiment do
|
|
|
425
464
|
end
|
|
426
465
|
|
|
427
466
|
it "should reset an experiment if it is loaded with different goals" do
|
|
428
|
-
|
|
467
|
+
same_but_different_goals
|
|
429
468
|
expect(Split::ExperimentCatalog.find("link_color").goals).to eq(["purchase", "refund"])
|
|
430
469
|
end
|
|
431
470
|
|
|
@@ -458,7 +497,7 @@ describe Split::Experiment do
|
|
|
458
497
|
expect(experiment.alternatives[0].p_winner).to be_within(0.04).of(0.50)
|
|
459
498
|
end
|
|
460
499
|
|
|
461
|
-
it "should calculate the probability of being the winning alternative separately for each goal" do
|
|
500
|
+
it "should calculate the probability of being the winning alternative separately for each goal", :skip => true do
|
|
462
501
|
experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green')
|
|
463
502
|
goal1 = experiment.goals[0]
|
|
464
503
|
goal2 = experiment.goals[1]
|
data/spec/helper_spec.rb
CHANGED
|
@@ -35,6 +35,18 @@ describe Split::Helper do
|
|
|
35
35
|
expect(lambda { ab_test({'link_color' => "purchase"}, 'blue', 'red') }).not_to raise_error
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
it "raises an appropriate error when processing combined expirements" do
|
|
39
|
+
Split.configuration.experiments = {
|
|
40
|
+
:combined_exp_1 => {
|
|
41
|
+
:alternatives => [ { name: "control", percent: 50 }, { name: "test-alt", percent: 50 } ],
|
|
42
|
+
:metric => :my_metric,
|
|
43
|
+
:combined_experiments => [:combined_exp_1_sub_1]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
Split::ExperimentCatalog.find_or_create('combined_exp_1')
|
|
47
|
+
expect(lambda { ab_test('combined_exp_1')}).to raise_error(Split::InvalidExperimentsFormatError )
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
it "should assign a random alternative to a new user when there are an equal number of alternatives assigned" do
|
|
39
51
|
ab_test('link_color', 'blue', 'red')
|
|
40
52
|
expect(['red', 'blue']).to include(ab_user['link_color'])
|
|
@@ -171,8 +183,7 @@ describe Split::Helper do
|
|
|
171
183
|
ab_test('link_color', {'blue' => 0.01}, 'red' => 0.2)
|
|
172
184
|
experiment = Split::ExperimentCatalog.find('link_color')
|
|
173
185
|
expect(experiment.alternatives.map(&:name)).to eq(['blue', 'red'])
|
|
174
|
-
|
|
175
|
-
# expect(experiment.alternatives.collect{|a| a.weight}).to eq([0.01, 0.2])
|
|
186
|
+
expect(experiment.alternatives.collect{|a| a.weight}).to match_array([0.01, 0.2])
|
|
176
187
|
end
|
|
177
188
|
|
|
178
189
|
it "should only let a user participate in one experiment at a time" do
|
|
@@ -285,98 +296,126 @@ describe Split::Helper do
|
|
|
285
296
|
end
|
|
286
297
|
|
|
287
298
|
describe 'ab_finished' do
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
299
|
+
context 'for an experiment that the user participates in' do
|
|
300
|
+
before(:each) do
|
|
301
|
+
@experiment_name = 'link_color'
|
|
302
|
+
@alternatives = ['blue', 'red']
|
|
303
|
+
@experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives)
|
|
304
|
+
@alternative_name = ab_test(@experiment_name, *@alternatives)
|
|
305
|
+
@previous_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
|
306
|
+
end
|
|
295
307
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
308
|
+
it 'should increment the counter for the completed alternative' do
|
|
309
|
+
ab_finished(@experiment_name)
|
|
310
|
+
new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
|
311
|
+
expect(new_completion_count).to eq(@previous_completion_count + 1)
|
|
312
|
+
end
|
|
301
313
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
314
|
+
it "should set experiment's finished key if reset is false" do
|
|
315
|
+
ab_finished(@experiment_name, {:reset => false})
|
|
316
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
317
|
+
expect(ab_user[@experiment.finished_key]).to eq(true)
|
|
318
|
+
end
|
|
307
319
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
320
|
+
it 'should not increment the counter if reset is false and the experiment has been already finished' do
|
|
321
|
+
2.times { ab_finished(@experiment_name, {:reset => false}) }
|
|
322
|
+
new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
|
323
|
+
expect(new_completion_count).to eq(@previous_completion_count + 1)
|
|
324
|
+
end
|
|
313
325
|
|
|
314
|
-
|
|
315
|
-
|
|
326
|
+
it 'should not increment the counter for an ended experiment' do
|
|
327
|
+
e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big')
|
|
328
|
+
e.winner = 'small'
|
|
329
|
+
a = ab_test('button_size', 'small', 'big')
|
|
330
|
+
expect(a).to eq('small')
|
|
331
|
+
expect(lambda {
|
|
332
|
+
ab_finished('button_size')
|
|
333
|
+
}).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
|
|
334
|
+
end
|
|
316
335
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
ab_finished('button_size')
|
|
323
|
-
}).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
|
|
324
|
-
end
|
|
336
|
+
it "should clear out the user's participation from their session" do
|
|
337
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
338
|
+
ab_finished(@experiment_name)
|
|
339
|
+
expect(ab_user.keys).to be_empty
|
|
340
|
+
end
|
|
325
341
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
ab_finished('button_size')
|
|
333
|
-
}).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
|
|
334
|
-
end
|
|
342
|
+
it "should not clear out the users session if reset is false" do
|
|
343
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
344
|
+
ab_finished(@experiment_name, {:reset => false})
|
|
345
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
346
|
+
expect(ab_user[@experiment.finished_key]).to eq(true)
|
|
347
|
+
end
|
|
335
348
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
349
|
+
it "should reset the users session when experiment is not versioned" do
|
|
350
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
351
|
+
ab_finished(@experiment_name)
|
|
352
|
+
expect(ab_user.keys).to be_empty
|
|
353
|
+
end
|
|
341
354
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
346
|
-
expect(ab_user[@experiment.finished_key]).to eq(true)
|
|
347
|
-
end
|
|
355
|
+
it "should reset the users session when experiment is versioned" do
|
|
356
|
+
@experiment.increment_version
|
|
357
|
+
@alternative_name = ab_test(@experiment_name, *@alternatives)
|
|
348
358
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
end
|
|
359
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
360
|
+
ab_finished(@experiment_name)
|
|
361
|
+
expect(ab_user.keys).to be_empty
|
|
362
|
+
end
|
|
354
363
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
364
|
+
context "when on_trial_complete is set" do
|
|
365
|
+
before { Split.configuration.on_trial_complete = :some_method }
|
|
366
|
+
it "should call the method" do
|
|
367
|
+
expect(self).to receive(:some_method)
|
|
368
|
+
ab_finished(@experiment_name)
|
|
369
|
+
end
|
|
358
370
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
371
|
+
it "should not call the method without alternative" do
|
|
372
|
+
ab_user[@experiment.key] = nil
|
|
373
|
+
expect(self).not_to receive(:some_method)
|
|
374
|
+
ab_finished(@experiment_name)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
362
377
|
end
|
|
363
378
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
379
|
+
context 'for an experiment that the user is excluded from' do
|
|
380
|
+
before do
|
|
381
|
+
alternative = ab_test('link_color', 'blue', 'red')
|
|
382
|
+
expect(Split::Alternative.new(alternative, 'link_color').participant_count).to eq(1)
|
|
383
|
+
alternative = ab_test('button_size', 'small', 'big')
|
|
384
|
+
expect(Split::Alternative.new(alternative, 'button_size').participant_count).to eq(0)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
it 'should not increment the completed counter' do
|
|
388
|
+
# So, user should be participating in the link_color experiment and
|
|
389
|
+
# receive the control for button_size. As the user is not participating in
|
|
390
|
+
# the button size experiment, finishing it should not increase the
|
|
391
|
+
# completion count for that alternative.
|
|
392
|
+
expect(lambda {
|
|
393
|
+
ab_finished('button_size')
|
|
394
|
+
}).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
|
|
395
|
+
end
|
|
367
396
|
end
|
|
368
397
|
|
|
369
|
-
context
|
|
370
|
-
before
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
398
|
+
context 'for an experiment that the user does not participate in' do
|
|
399
|
+
before do
|
|
400
|
+
Split::ExperimentCatalog.find_or_create(:not_started_experiment, 'control', 'alt')
|
|
401
|
+
end
|
|
402
|
+
it 'should not raise an exception' do
|
|
403
|
+
expect { ab_finished(:not_started_experiment) }.not_to raise_exception
|
|
374
404
|
end
|
|
375
405
|
|
|
376
|
-
it
|
|
377
|
-
ab_user[
|
|
378
|
-
|
|
379
|
-
|
|
406
|
+
it 'should not change the user state when reset is false' do
|
|
407
|
+
expect { ab_finished(:not_started_experiment, reset: false) }.not_to change { ab_user.keys}.from([])
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
it 'should not change the user state when reset is true' do
|
|
411
|
+
expect(self).not_to receive(:reset!)
|
|
412
|
+
ab_finished(:not_started_experiment)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
it 'should not increment the completed counter' do
|
|
416
|
+
ab_finished(:not_started_experiment)
|
|
417
|
+
expect(Split::Alternative.new('control', :not_started_experiment).completed_count).to eq(0)
|
|
418
|
+
expect(Split::Alternative.new('alt', :not_started_experiment).completed_count).to eq(0)
|
|
380
419
|
end
|
|
381
420
|
end
|
|
382
421
|
end
|
|
@@ -516,6 +555,17 @@ describe Split::Helper do
|
|
|
516
555
|
expect(active_experiments.first[0]).to eq "link_color"
|
|
517
556
|
end
|
|
518
557
|
|
|
558
|
+
it 'should show versioned tests properly' do
|
|
559
|
+
10.times { experiment.reset }
|
|
560
|
+
|
|
561
|
+
alternative = ab_test(experiment.name, 'blue', 'red')
|
|
562
|
+
ab_finished(experiment.name, reset: false)
|
|
563
|
+
|
|
564
|
+
expect(experiment.version).to eq(10)
|
|
565
|
+
expect(active_experiments.count).to eq 1
|
|
566
|
+
expect(active_experiments).to eq({'link_color' => alternative })
|
|
567
|
+
end
|
|
568
|
+
|
|
519
569
|
it 'should show multiple tests' do
|
|
520
570
|
Split.configure do |config|
|
|
521
571
|
config.allow_multiple_experiments = true
|
|
@@ -533,7 +583,7 @@ describe Split::Helper do
|
|
|
533
583
|
end
|
|
534
584
|
e = Split::ExperimentCatalog.find_or_create('def', '4', '5', '6')
|
|
535
585
|
e.winner = '4'
|
|
536
|
-
|
|
586
|
+
ab_test('def', '4', '5', '6')
|
|
537
587
|
another_alternative = ab_test('ghi', '7', '8', '9')
|
|
538
588
|
expect(active_experiments.count).to eq 1
|
|
539
589
|
expect(active_experiments.first[0]).to eq "ghi"
|
|
@@ -552,6 +602,11 @@ describe Split::Helper do
|
|
|
552
602
|
expect(alternative).to eq experiment.control.name
|
|
553
603
|
end
|
|
554
604
|
|
|
605
|
+
it 'should not create a experiment' do
|
|
606
|
+
ab_test('link_color', 'blue', 'red')
|
|
607
|
+
expect(Split::Experiment.new('link_color')).to be_a_new_record
|
|
608
|
+
end
|
|
609
|
+
|
|
555
610
|
it "should not increment the participation count" do
|
|
556
611
|
|
|
557
612
|
previous_red_count = Split::Alternative.new('red', 'link_color').participant_count
|
|
@@ -687,6 +742,14 @@ describe Split::Helper do
|
|
|
687
742
|
end
|
|
688
743
|
end
|
|
689
744
|
|
|
745
|
+
describe 'when user is previewing' do
|
|
746
|
+
before(:each) do
|
|
747
|
+
@request = OpenStruct.new(headers: { 'x-purpose' => 'preview' })
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
it_behaves_like "a disabled test"
|
|
751
|
+
end
|
|
752
|
+
|
|
690
753
|
describe 'versioned experiments' do
|
|
691
754
|
it "should use version zero if no version is present" do
|
|
692
755
|
alternative_name = ab_test('link_color', 'blue', 'red')
|
|
@@ -996,8 +1059,8 @@ describe Split::Helper do
|
|
|
996
1059
|
|
|
997
1060
|
it 'should handle multiple experiments correctly' do
|
|
998
1061
|
experiment2 = Split::ExperimentCatalog.find_or_create('link_color2', 'blue', 'red')
|
|
999
|
-
|
|
1000
|
-
|
|
1062
|
+
ab_test('link_color', 'blue', 'red')
|
|
1063
|
+
ab_test('link_color2', 'blue', 'red')
|
|
1001
1064
|
ab_finished('link_color2')
|
|
1002
1065
|
|
|
1003
1066
|
experiment2.alternatives.each do |alt|
|
|
@@ -1,39 +1,106 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "spec_helper"
|
|
3
|
+
require 'rack/test'
|
|
3
4
|
|
|
4
5
|
describe Split::Persistence::CookieAdapter do
|
|
6
|
+
subject { described_class.new(context) }
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
shared_examples "sets cookies correctly" do
|
|
9
|
+
describe "#[] and #[]=" do
|
|
10
|
+
it "set and return the value for given key" do
|
|
11
|
+
subject["my_key"] = "my_value"
|
|
12
|
+
expect(subject["my_key"]).to eq("my_value")
|
|
13
|
+
end
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
it "handles invalid JSON" do
|
|
16
|
+
context.request.cookies[:split] = {
|
|
17
|
+
:value => '{"foo":2,',
|
|
18
|
+
:expires => Time.now
|
|
19
|
+
}
|
|
20
|
+
expect(subject["my_key"]).to be_nil
|
|
21
|
+
subject["my_key"] = "my_value"
|
|
22
|
+
expect(subject["my_key"]).to eq("my_value")
|
|
23
|
+
end
|
|
13
24
|
end
|
|
14
|
-
end
|
|
15
25
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
describe "#delete" do
|
|
27
|
+
it "should delete the given key" do
|
|
28
|
+
subject["my_key"] = "my_value"
|
|
29
|
+
subject.delete("my_key")
|
|
30
|
+
expect(subject["my_key"]).to be_nil
|
|
31
|
+
end
|
|
21
32
|
end
|
|
22
|
-
end
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
describe "#keys" do
|
|
35
|
+
it "should return an array of the session's stored keys" do
|
|
36
|
+
subject["my_key"] = "my_value"
|
|
37
|
+
subject["my_second_key"] = "my_second_value"
|
|
38
|
+
expect(subject.keys).to match(["my_key", "my_second_key"])
|
|
39
|
+
end
|
|
29
40
|
end
|
|
30
41
|
end
|
|
31
42
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
|
|
44
|
+
context "when using Rack" do
|
|
45
|
+
let(:env) { Rack::MockRequest.env_for("http://example.com:8080/") }
|
|
46
|
+
let(:request) { Rack::Request.new(env) }
|
|
47
|
+
let(:response) { Rack::MockResponse.new(200, {}, "") }
|
|
48
|
+
let(:context) { double(request: request, response: response, cookies: CookiesMock.new) }
|
|
49
|
+
|
|
50
|
+
include_examples "sets cookies correctly"
|
|
51
|
+
|
|
52
|
+
it "puts multiple experiments in a single cookie" do
|
|
53
|
+
subject["foo"] = "FOO"
|
|
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/)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "ensure other added cookies are not overriden" do
|
|
59
|
+
context.response.set_cookie 'dummy', 'wow'
|
|
60
|
+
subject["foo"] = "FOO"
|
|
61
|
+
expect(context.response.headers["Set-Cookie"]).to include("dummy=wow")
|
|
62
|
+
expect(context.response.headers["Set-Cookie"]).to include("split=")
|
|
63
|
+
end
|
|
37
64
|
end
|
|
38
65
|
|
|
66
|
+
context "when @context is an ActionController::Base" do
|
|
67
|
+
before :context do
|
|
68
|
+
require "rails"
|
|
69
|
+
require "action_controller/railtie"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
let(:context) do
|
|
73
|
+
controller = controller_class.new
|
|
74
|
+
if controller.respond_to?(:set_request!)
|
|
75
|
+
controller.set_request!(ActionDispatch::Request.new({}))
|
|
76
|
+
else # Before rails 5.0
|
|
77
|
+
controller.send(:"request=", ActionDispatch::Request.new({}))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
response = ActionDispatch::Response.new(200, {}, '').tap do |res|
|
|
81
|
+
res.request = controller.request
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if controller.respond_to?(:set_response!)
|
|
85
|
+
controller.set_response!(response)
|
|
86
|
+
else # Before rails 5.0
|
|
87
|
+
controller.send(:set_response!, response)
|
|
88
|
+
end
|
|
89
|
+
controller
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
let(:controller_class) { Class.new(ActionController::Base) }
|
|
93
|
+
|
|
94
|
+
include_examples "sets cookies correctly"
|
|
95
|
+
|
|
96
|
+
it "puts multiple experiments in a single cookie" do
|
|
97
|
+
subject["foo"] = "FOO"
|
|
98
|
+
subject["bar"] = "BAR"
|
|
99
|
+
expect(subject.keys).to eq(["foo", "bar"])
|
|
100
|
+
expect(subject["foo"]).to eq("FOO")
|
|
101
|
+
expect(subject["bar"]).to eq("BAR")
|
|
102
|
+
cookie_jar = context.request.env["action_dispatch.cookies"]
|
|
103
|
+
expect(cookie_jar['split']).to eq("{\"foo\":\"FOO\",\"bar\":\"BAR\"}")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
39
106
|
end
|