split 3.0.0 → 4.0.1

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 (82) hide show
  1. checksums.yaml +5 -5
  2. data/.eslintrc +1 -1
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  5. data/.github/dependabot.yml +7 -0
  6. data/.github/workflows/ci.yml +71 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +71 -1044
  9. data/.rubocop_todo.yml +226 -0
  10. data/Appraisals +12 -1
  11. data/CHANGELOG.md +157 -0
  12. data/CODE_OF_CONDUCT.md +3 -3
  13. data/CONTRIBUTING.md +54 -5
  14. data/Gemfile +2 -0
  15. data/LICENSE +1 -1
  16. data/README.md +232 -121
  17. data/Rakefile +2 -0
  18. data/gemfiles/5.0.gemfile +1 -2
  19. data/gemfiles/{4.2.gemfile → 5.1.gemfile} +2 -2
  20. data/gemfiles/5.2.gemfile +9 -0
  21. data/gemfiles/6.0.gemfile +9 -0
  22. data/gemfiles/6.1.gemfile +9 -0
  23. data/gemfiles/7.0.gemfile +9 -0
  24. data/lib/split/algorithms/block_randomization.rb +2 -0
  25. data/lib/split/algorithms/weighted_sample.rb +2 -1
  26. data/lib/split/algorithms/whiplash.rb +3 -2
  27. data/lib/split/alternative.rb +7 -3
  28. data/lib/split/cache.rb +28 -0
  29. data/lib/split/combined_experiments_helper.rb +38 -0
  30. data/lib/split/configuration.rb +24 -13
  31. data/lib/split/dashboard/helpers.rb +3 -2
  32. data/lib/split/dashboard/pagination_helpers.rb +87 -0
  33. data/lib/split/dashboard/paginator.rb +17 -0
  34. data/lib/split/dashboard/public/dashboard.js +10 -0
  35. data/lib/split/dashboard/public/style.css +14 -0
  36. data/lib/split/dashboard/views/_controls.erb +13 -0
  37. data/lib/split/dashboard/views/index.erb +5 -1
  38. data/lib/split/dashboard/views/layout.erb +1 -1
  39. data/lib/split/dashboard.rb +21 -1
  40. data/lib/split/encapsulated_helper.rb +3 -2
  41. data/lib/split/engine.rb +7 -2
  42. data/lib/split/exceptions.rb +1 -0
  43. data/lib/split/experiment.rb +103 -69
  44. data/lib/split/experiment_catalog.rb +1 -3
  45. data/lib/split/extensions/string.rb +1 -0
  46. data/lib/split/goals_collection.rb +2 -0
  47. data/lib/split/helper.rb +42 -9
  48. data/lib/split/metric.rb +2 -1
  49. data/lib/split/persistence/cookie_adapter.rb +58 -15
  50. data/lib/split/persistence/dual_adapter.rb +54 -12
  51. data/lib/split/persistence/redis_adapter.rb +5 -0
  52. data/lib/split/persistence/session_adapter.rb +1 -0
  53. data/lib/split/persistence.rb +4 -2
  54. data/lib/split/redis_interface.rb +9 -30
  55. data/lib/split/trial.rb +25 -17
  56. data/lib/split/user.rb +20 -4
  57. data/lib/split/version.rb +2 -4
  58. data/lib/split/zscore.rb +1 -0
  59. data/lib/split.rb +17 -3
  60. data/spec/alternative_spec.rb +13 -1
  61. data/spec/cache_spec.rb +88 -0
  62. data/spec/combined_experiments_helper_spec.rb +57 -0
  63. data/spec/configuration_spec.rb +17 -15
  64. data/spec/dashboard/pagination_helpers_spec.rb +200 -0
  65. data/spec/dashboard/paginator_spec.rb +37 -0
  66. data/spec/dashboard_helpers_spec.rb +2 -2
  67. data/spec/dashboard_spec.rb +78 -17
  68. data/spec/encapsulated_helper_spec.rb +2 -2
  69. data/spec/experiment_spec.rb +117 -13
  70. data/spec/goals_collection_spec.rb +1 -1
  71. data/spec/helper_spec.rb +211 -112
  72. data/spec/persistence/cookie_adapter_spec.rb +90 -23
  73. data/spec/persistence/dual_adapter_spec.rb +160 -68
  74. data/spec/persistence/redis_adapter_spec.rb +9 -0
  75. data/spec/redis_interface_spec.rb +0 -69
  76. data/spec/spec_helper.rb +5 -6
  77. data/spec/split_spec.rb +7 -7
  78. data/spec/trial_spec.rb +65 -19
  79. data/spec/user_spec.rb +45 -3
  80. data/split.gemspec +20 -10
  81. metadata +61 -35
  82. data/.travis.yml +0 -16
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
- # TODO: persist alternative weights
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
@@ -218,13 +229,15 @@ describe Split::Helper do
218
229
 
219
230
  context "when user already has experiment" do
220
231
  let(:mock_user){ Split::User.new(self, {'test_0' => 'test-alt'}) }
221
- before{
232
+
233
+ before do
222
234
  Split.configure do |config|
223
235
  config.allow_multiple_experiments = 'control'
224
236
  end
237
+
225
238
  Split::ExperimentCatalog.find_or_initialize('test_0', 'control', 'test-alt').save
226
239
  Split::ExperimentCatalog.find_or_initialize('test_1', 'control', 'test-alt').save
227
- }
240
+ end
228
241
 
229
242
  it "should restore previously selected alternative" do
230
243
  expect(ab_user.active_experiments.size).to eq 1
@@ -232,6 +245,16 @@ describe Split::Helper do
232
245
  expect(ab_test(:test_0, {'control' => 1}, {"test-alt" => 100})).to eq 'test-alt'
233
246
  end
234
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
+
235
258
  it "lets override existing choice" do
236
259
  pending "this requires user store reset on first call not depending on whelther it is current trial"
237
260
  @params = { 'ab_test' => { 'test_1' => 'test-alt' } }
@@ -254,129 +277,187 @@ describe Split::Helper do
254
277
  end
255
278
 
256
279
  describe 'metadata' do
257
- before do
258
- Split.configuration.experiments = {
259
- :my_experiment => {
260
- :alternatives => ["one", "two"],
261
- :resettable => false,
262
- :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
+ }
263
288
  }
264
- }
265
- end
289
+ end
290
+
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
266
303
 
267
- it 'should be passed to helper block' do
268
- @params = { 'ab_test' => { 'my_experiment' => 'one' } }
269
- expect(ab_test('my_experiment')).to eq 'one'
270
- expect(ab_test('my_experiment') do |alternative, meta|
271
- meta
272
- end).to eq('Meta1')
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
273
309
  end
274
310
 
275
- it 'should pass empty hash to helper block if library disabled' do
276
- Split.configure do |config|
277
- 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
+ }
278
320
  end
279
321
 
280
- expect(ab_test('my_experiment')).to eq 'one'
281
- expect(ab_test('my_experiment') do |_, meta|
282
- meta
283
- end).to eq({})
322
+ it 'should be passed to helper block' do
323
+ expect(ab_test('my_experiment') do |alternative, meta|
324
+ meta
325
+ end).to eq({})
326
+ end
327
+
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
284
337
  end
285
338
  end
286
339
 
287
340
  describe 'ab_finished' do
288
- before(:each) do
289
- @experiment_name = 'link_color'
290
- @alternatives = ['blue', 'red']
291
- @experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives)
292
- @alternative_name = ab_test(@experiment_name, *@alternatives)
293
- @previous_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
294
- end
341
+ context 'for an experiment that the user participates in' do
342
+ before(:each) do
343
+ @experiment_name = 'link_color'
344
+ @alternatives = ['blue', 'red']
345
+ @experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives)
346
+ @alternative_name = ab_test(@experiment_name, *@alternatives)
347
+ @previous_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
348
+ end
295
349
 
296
- it 'should increment the counter for the completed alternative' do
297
- ab_finished(@experiment_name)
298
- new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
299
- expect(new_completion_count).to eq(@previous_completion_count + 1)
300
- end
350
+ it 'should increment the counter for the completed alternative' do
351
+ ab_finished(@experiment_name)
352
+ new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
353
+ expect(new_completion_count).to eq(@previous_completion_count + 1)
354
+ end
301
355
 
302
- it "should set experiment's finished key if reset is false" do
303
- ab_finished(@experiment_name, {:reset => false})
304
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
305
- expect(ab_user[@experiment.finished_key]).to eq(true)
306
- end
356
+ it "should set experiment's finished key if reset is false" do
357
+ ab_finished(@experiment_name, {:reset => false})
358
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
359
+ expect(ab_user[@experiment.finished_key]).to eq(true)
360
+ end
307
361
 
308
- it 'should not increment the counter if reset is false and the experiment has been already finished' do
309
- 2.times { ab_finished(@experiment_name, {:reset => false}) }
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
362
+ it 'should not increment the counter if reset is false and the experiment has been already finished' do
363
+ 2.times { ab_finished(@experiment_name, {:reset => false}) }
364
+ new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
365
+ expect(new_completion_count).to eq(@previous_completion_count + 1)
366
+ end
313
367
 
314
- it 'should not increment the counter for an experiment that the user is not participating in' do
315
- ab_test('button_size', 'small', 'big')
368
+ it 'should not increment the counter for an ended experiment' do
369
+ e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big')
370
+ e.winner = 'small'
371
+ a = ab_test('button_size', 'small', 'big')
372
+ expect(a).to eq('small')
373
+ expect(lambda {
374
+ ab_finished('button_size')
375
+ }).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
376
+ end
316
377
 
317
- # So, user should be participating in the link_color experiment and
318
- # receive the control for button_size. As the user is not participating in
319
- # the button size experiment, finishing it should not increase the
320
- # completion count for that alternative.
321
- expect(lambda {
322
- ab_finished('button_size')
323
- }).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
324
- end
378
+ it "should clear out the user's participation from their session" do
379
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
380
+ ab_finished(@experiment_name)
381
+ expect(ab_user.keys).to be_empty
382
+ end
325
383
 
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
384
+ it "should not clear out the users session if reset is false" do
385
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
386
+ ab_finished(@experiment_name, {:reset => false})
387
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
388
+ expect(ab_user[@experiment.finished_key]).to eq(true)
389
+ end
335
390
 
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
391
+ it "should reset the users session when experiment is not versioned" do
392
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
393
+ ab_finished(@experiment_name)
394
+ expect(ab_user.keys).to be_empty
395
+ end
341
396
 
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
397
+ it "should reset the users session when experiment is versioned" do
398
+ @experiment.increment_version
399
+ @alternative_name = ab_test(@experiment_name, *@alternatives)
348
400
 
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
401
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
402
+ ab_finished(@experiment_name)
403
+ expect(ab_user.keys).to be_empty
404
+ end
354
405
 
355
- it "should reset the users session when experiment is versioned" do
356
- @experiment.increment_version
357
- @alternative_name = ab_test(@experiment_name, *@alternatives)
406
+ context "when on_trial_complete is set" do
407
+ before { Split.configuration.on_trial_complete = :some_method }
408
+ it "should call the method" do
409
+ expect(self).to receive(:some_method)
410
+ ab_finished(@experiment_name)
411
+ end
358
412
 
359
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
360
- ab_finished(@experiment_name)
361
- expect(ab_user.keys).to be_empty
413
+ it "should not call the method without alternative" do
414
+ ab_user[@experiment.key] = nil
415
+ expect(self).not_to receive(:some_method)
416
+ ab_finished(@experiment_name)
417
+ end
418
+ end
362
419
  end
363
420
 
364
- it "should do nothing where the experiment was not started by this user" do
365
- ab_user = nil
366
- expect(lambda { ab_finished('some_experiment_not_started_by_the_user') }).not_to raise_exception
421
+ context 'for an experiment that the user is excluded from' do
422
+ before do
423
+ alternative = ab_test('link_color', 'blue', 'red')
424
+ expect(Split::Alternative.new(alternative, 'link_color').participant_count).to eq(1)
425
+ alternative = ab_test('button_size', 'small', 'big')
426
+ expect(Split::Alternative.new(alternative, 'button_size').participant_count).to eq(0)
427
+ end
428
+
429
+ it 'should not increment the completed counter' do
430
+ # So, user should be participating in the link_color experiment and
431
+ # receive the control for button_size. As the user is not participating in
432
+ # the button size experiment, finishing it should not increase the
433
+ # completion count for that alternative.
434
+ expect(lambda {
435
+ ab_finished('button_size')
436
+ }).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
437
+ end
367
438
  end
368
439
 
369
- context "when on_trial_complete is set" do
370
- before { Split.configuration.on_trial_complete = :some_method }
371
- it "should call the method" do
372
- expect(self).to receive(:some_method)
373
- ab_finished(@experiment_name)
440
+ context 'for an experiment that the user does not participate in' do
441
+ before do
442
+ Split::ExperimentCatalog.find_or_create(:not_started_experiment, 'control', 'alt')
443
+ end
444
+ it 'should not raise an exception' do
445
+ expect { ab_finished(:not_started_experiment) }.not_to raise_exception
374
446
  end
375
447
 
376
- it "should not call the method without alternative" do
377
- ab_user[@experiment.key] = nil
378
- expect(self).not_to receive(:some_method)
379
- ab_finished(@experiment_name)
448
+ it 'should not change the user state when reset is false' do
449
+ expect { ab_finished(:not_started_experiment, reset: false) }.not_to change { ab_user.keys}.from([])
450
+ end
451
+
452
+ it 'should not change the user state when reset is true' do
453
+ expect(self).not_to receive(:reset!)
454
+ ab_finished(:not_started_experiment)
455
+ end
456
+
457
+ it 'should not increment the completed counter' do
458
+ ab_finished(:not_started_experiment)
459
+ expect(Split::Alternative.new('control', :not_started_experiment).completed_count).to eq(0)
460
+ expect(Split::Alternative.new('alt', :not_started_experiment).completed_count).to eq(0)
380
461
  end
381
462
  end
382
463
  end
@@ -516,6 +597,17 @@ describe Split::Helper do
516
597
  expect(active_experiments.first[0]).to eq "link_color"
517
598
  end
518
599
 
600
+ it 'should show versioned tests properly' do
601
+ 10.times { experiment.reset }
602
+
603
+ alternative = ab_test(experiment.name, 'blue', 'red')
604
+ ab_finished(experiment.name, reset: false)
605
+
606
+ expect(experiment.version).to eq(10)
607
+ expect(active_experiments.count).to eq 1
608
+ expect(active_experiments).to eq({'link_color' => alternative })
609
+ end
610
+
519
611
  it 'should show multiple tests' do
520
612
  Split.configure do |config|
521
613
  config.allow_multiple_experiments = true
@@ -533,7 +625,7 @@ describe Split::Helper do
533
625
  end
534
626
  e = Split::ExperimentCatalog.find_or_create('def', '4', '5', '6')
535
627
  e.winner = '4'
536
- alternative = ab_test('def', '4', '5', '6')
628
+ ab_test('def', '4', '5', '6')
537
629
  another_alternative = ab_test('ghi', '7', '8', '9')
538
630
  expect(active_experiments.count).to eq 1
539
631
  expect(active_experiments.first[0]).to eq "ghi"
@@ -552,6 +644,11 @@ describe Split::Helper do
552
644
  expect(alternative).to eq experiment.control.name
553
645
  end
554
646
 
647
+ it 'should not create a experiment' do
648
+ ab_test('link_color', 'blue', 'red')
649
+ expect(Split::Experiment.new('link_color')).to be_a_new_record
650
+ end
651
+
555
652
  it "should not increment the participation count" do
556
653
 
557
654
  previous_red_count = Split::Alternative.new('red', 'link_color').participant_count
@@ -687,6 +784,14 @@ describe Split::Helper do
687
784
  end
688
785
  end
689
786
 
787
+ describe 'when user is previewing' do
788
+ before(:each) do
789
+ @request = OpenStruct.new(headers: { 'x-purpose' => 'preview' })
790
+ end
791
+
792
+ it_behaves_like "a disabled test"
793
+ end
794
+
690
795
  describe 'versioned experiments' do
691
796
  it "should use version zero if no version is present" do
692
797
  alternative_name = ab_test('link_color', 'blue', 'red')
@@ -996,8 +1101,8 @@ describe Split::Helper do
996
1101
 
997
1102
  it 'should handle multiple experiments correctly' do
998
1103
  experiment2 = Split::ExperimentCatalog.find_or_create('link_color2', 'blue', 'red')
999
- alternative_name = ab_test('link_color', 'blue', 'red')
1000
- alternative_name2 = ab_test('link_color2', 'blue', 'red')
1104
+ ab_test('link_color', 'blue', 'red')
1105
+ ab_test('link_color2', 'blue', 'red')
1001
1106
  ab_finished('link_color2')
1002
1107
 
1003
1108
  experiment2.alternatives.each do |alt|
@@ -1033,15 +1138,9 @@ describe Split::Helper do
1033
1138
  end
1034
1139
 
1035
1140
  it "should increment the counter for the specified-goal completed alternative" do
1036
- expect(lambda {
1037
- expect(lambda {
1038
- ab_finished({"link_color" => ["purchase"]})
1039
- }).not_to change {
1040
- Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
1041
- }
1042
- }).to change {
1043
- Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
1044
- }.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)
1045
1144
  end
1046
1145
  end
1047
1146
  end
@@ -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
- let(:context) { double(:cookies => CookiesMock.new) }
7
- subject { Split::Persistence::CookieAdapter.new(context) }
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
- describe "#[] and #[]=" do
10
- it "should set and return the value for given key" do
11
- subject["my_key"] = "my_value"
12
- expect(subject["my_key"]).to eq("my_value")
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
- describe "#delete" do
17
- it "should delete the given key" do
18
- subject["my_key"] = "my_value"
19
- subject.delete("my_key")
20
- expect(subject["my_key"]).to be_nil
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
- describe "#keys" do
25
- it "should return an array of the session's stored keys" do
26
- subject["my_key"] = "my_value"
27
- subject["my_second_key"] = "my_second_value"
28
- expect(subject.keys).to match(["my_key", "my_second_key"])
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
- it "handles invalid JSON" do
33
- context.cookies[:split] = { :value => '{"foo":2,', :expires => Time.now }
34
- expect(subject["my_key"]).to be_nil
35
- subject["my_key"] = "my_value"
36
- expect(subject["my_key"]).to eq("my_value")
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} [A-Z]{3}\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