vanity 2.2.10 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +8 -10
  3. data/Appraisals +9 -19
  4. data/CHANGELOG +7 -1
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +20 -12
  7. data/README.md +11 -13
  8. data/gemfiles/rails42.gemfile +2 -2
  9. data/gemfiles/rails42.gemfile.lock +89 -84
  10. data/gemfiles/rails42_protected_attributes.gemfile +2 -2
  11. data/gemfiles/rails42_protected_attributes.gemfile.lock +84 -79
  12. data/gemfiles/{rails41.gemfile → rails51.gemfile} +3 -3
  13. data/gemfiles/rails51.gemfile.lock +262 -0
  14. data/gemfiles/{rails5.gemfile → rails52.gemfile} +2 -2
  15. data/gemfiles/rails52.gemfile.lock +270 -0
  16. data/lib/generators/templates/{add_participants_unique_index_migration.rb → add_participants_unique_index_migration.rb.erb} +1 -1
  17. data/lib/generators/templates/{add_unique_indexes_migration.rb → add_unique_indexes_migration.rb.erb} +1 -1
  18. data/lib/generators/templates/{vanity_migration.rb → vanity_migration.rb.erb} +1 -1
  19. data/lib/generators/vanity/migration_generator.rb +34 -0
  20. data/lib/vanity/adapters/redis_adapter.rb +18 -18
  21. data/lib/vanity/version.rb +1 -1
  22. data/test/adapters/redis_adapter_test.rb +8 -12
  23. data/test/adapters/shared_tests.rb +7 -6
  24. data/test/commands/report_test.rb +13 -1
  25. data/test/configuration_test.rb +15 -2
  26. data/test/dummy/app/mailers/vanity_mailer.rb +3 -1
  27. data/test/dummy/config/routes.rb +17 -3
  28. data/test/experiment/ab_test.rb +3 -3
  29. data/test/frameworks/rails/action_controller_test.rb +12 -6
  30. data/test/frameworks/rails/action_mailer_test.rb +0 -1
  31. data/test/metric/active_record_test.rb +8 -2
  32. data/test/test_helper.rb +57 -10
  33. data/test/web/rails/dashboard_test.rb +19 -10
  34. data/vanity.gemspec +1 -1
  35. metadata +14 -19
  36. data/gemfiles/rails32.gemfile +0 -36
  37. data/gemfiles/rails32.gemfile.lock +0 -242
  38. data/gemfiles/rails41.gemfile.lock +0 -230
  39. data/gemfiles/rails5.gemfile.lock +0 -256
  40. data/lib/generators/vanity/add_participants_unique_index_generator.rb +0 -15
  41. data/lib/generators/vanity/add_unique_indexes_generator.rb +0 -15
  42. data/lib/generators/vanity_generator.rb +0 -15
@@ -1,5 +1,5 @@
1
1
  module Vanity
2
- VERSION = "2.2.10"
2
+ VERSION = "3.0.0"
3
3
 
4
4
  module Version
5
5
  version = VERSION.to_s.split(".").map { |i| i.to_i }
@@ -18,17 +18,13 @@ describe Vanity::Adapters::RedisAdapter do
18
18
 
19
19
  include Vanity::Adapters::SharedTests
20
20
 
21
- it "warns on disconnect error" do
21
+ it "disconnects" do
22
22
  if defined?(Redis)
23
- assert_silent do
24
- Redis.any_instance.stubs(:connect!)
25
- mocked_redis = stub("Redis")
26
- mocked_redis.expects(:client).raises(RuntimeError)
27
- redis_adapter = Vanity::Adapters::RedisAdapter.new({})
28
- redis_adapter.stubs(:redis).returns(mocked_redis)
29
- Vanity.logger.expects(:warn).with("Error while disconnecting from redis: RuntimeError")
30
- redis_adapter.disconnect!
31
- end
23
+ mocked_redis = stub("Redis")
24
+ mocked_redis.expects(:disconnect!)
25
+ redis_adapter = Vanity::Adapters::RedisAdapter.new({})
26
+ redis_adapter.stubs(:redis).returns(mocked_redis)
27
+ redis_adapter.disconnect!
32
28
  end
33
29
  end
34
30
 
@@ -75,7 +71,7 @@ describe Vanity::Adapters::RedisAdapter do
75
71
 
76
72
  it "gracefully fails in #ab_show" do
77
73
  redis_adapter, mocked_redis = stub_redis
78
- mocked_redis.stubs(:[]=).raises(RuntimeError)
74
+ mocked_redis.stubs(:set).raises(RuntimeError)
79
75
 
80
76
  assert_silent do
81
77
  redis_adapter.ab_show("price_options", "3ff62e2fb51f0b22646a342a2d357aec", 0)
@@ -84,7 +80,7 @@ describe Vanity::Adapters::RedisAdapter do
84
80
 
85
81
  it "gracefully fails in #ab_showing" do
86
82
  redis_adapter, mocked_redis = stub_redis
87
- mocked_redis.stubs(:[]).raises(RuntimeError)
83
+ mocked_redis.stubs(:get).raises(RuntimeError)
88
84
 
89
85
  assert_silent do
90
86
  redis_adapter.ab_showing("price_options", "3ff62e2fb51f0b22646a342a2d357aec")
@@ -123,6 +123,10 @@ module Vanity::Adapters::SharedTests
123
123
  @subject.destroy_experiment(experiment)
124
124
 
125
125
  refute(@subject.experiment_persisted?(experiment))
126
+ assert_equal(
127
+ { :participants => 0, :converted => 0, :conversions => 0 },
128
+ @subject.ab_counts(experiment, alternative)
129
+ )
126
130
  end
127
131
  end
128
132
 
@@ -272,8 +276,7 @@ module Vanity::Adapters::SharedTests
272
276
  end
273
277
 
274
278
  it 'returns nil if the identity has no assignment' do
275
- assert_equal(
276
- nil,
279
+ assert_nil(
277
280
  @subject.ab_assigned(experiment, identity)
278
281
  )
279
282
  end
@@ -301,8 +304,7 @@ module Vanity::Adapters::SharedTests
301
304
  end
302
305
 
303
306
  it 'returns nil otherwise' do
304
- assert_equal(
305
- nil,
307
+ assert_nil(
306
308
  @subject.ab_get_outcome(experiment)
307
309
  )
308
310
  end
@@ -324,8 +326,7 @@ module Vanity::Adapters::SharedTests
324
326
  @subject.ab_show(experiment, identity, alternative)
325
327
  @subject.ab_not_showing(experiment, identity)
326
328
 
327
- assert_equal(
328
- nil,
329
+ assert_nil(
329
330
  @subject.ab_showing(experiment, identity)
330
331
  )
331
332
  end
@@ -6,6 +6,15 @@ describe Vanity::Commands do
6
6
  metric "Coolness"
7
7
  end
8
8
 
9
+ def with_captured_stdout
10
+ original_stdout = $stdout
11
+ $stdout = StringIO.new
12
+ yield
13
+ $stdout.string
14
+ ensure
15
+ $stdout = original_stdout
16
+ end
17
+
9
18
  describe ".report" do
10
19
  describe "given file" do
11
20
  let(:file) { "tmp/config/redis.yml" }
@@ -20,7 +29,10 @@ describe Vanity::Commands do
20
29
  experiment(:foobar).choose
21
30
 
22
31
  FileUtils.mkpath "tmp/config"
23
- Vanity::Commands.report(file)
32
+
33
+ with_captured_stdout do
34
+ Vanity::Commands.report(file)
35
+ end
24
36
  end
25
37
 
26
38
  after do
@@ -113,6 +113,19 @@ describe Vanity::Configuration do
113
113
  end
114
114
 
115
115
  describe "setup_locales" do
116
- it "adds vanity locales to the gem"
116
+ it "adds vanity locales to the I18n gem" do
117
+ begin
118
+ original_load_path = I18n.load_path
119
+
120
+ config.setup_locales
121
+
122
+ assert_includes(
123
+ I18n.load_path,
124
+ File.expand_path(File.join(__FILE__, '..', '..', 'lib/vanity/locales/vanity.en.yml'))
125
+ )
126
+ ensure
127
+ I18n.load_path = original_load_path
128
+ end
129
+ end
117
130
  end
118
- end
131
+ end
@@ -12,8 +12,10 @@ class VanityMailer < ActionMailer::Base
12
12
  def ab_test_content(user)
13
13
  use_vanity_mailer user
14
14
 
15
+ image_html = view_context.vanity_tracking_image(Vanity.context.vanity_identity, :open, :host => "127.0.0.1:3000")
16
+
15
17
  mail do |format|
16
- format.html { render :text=>view_context.vanity_tracking_image(Vanity.context.vanity_identity, :open, :host => "127.0.0.1:3000") }
18
+ format.html { render :html=>image_html.html_safe }
17
19
  end
18
20
  end
19
21
  end
@@ -52,7 +52,21 @@ Dummy::Application.routes.draw do
52
52
 
53
53
  # See how all your routes lay out with "rake routes"
54
54
 
55
- # This is a legacy wild controller route that's not recommended for RESTful applications.
56
- # Note: This route will make all actions in every controller accessible via GET requests.
57
- match ':controller(/:action(/:id(.:format)))', :via => [:get, :post]
55
+ get '/use_vanity(/:id(.:format))', controller: :use_vanity, action: :index
56
+
57
+ %w(js view_helper_ab_test_js global_ab_test_js model_js).each do |action|
58
+ get "/use_vanity/#{action}(/:id(.:format))", controller: :use_vanity, action: action
59
+ end
60
+
61
+ %w(track test_capture test_render test_view).each do |action|
62
+ get "/ab_test/#{action}(/:id(.:format))", controller: :ab_test, action: action
63
+ end
64
+
65
+ %w(reset disable enable chooses add_participant complete).each do |action|
66
+ post "/vanity(/#{action}(/:id(.:format)))", controller: :vanity, action: action
67
+ end
68
+
69
+ %w(index participant image).each do |action|
70
+ get "/vanity(/#{action}(/:id(.:format)))", controller: :vanity, action: action
71
+ end
58
72
  end
@@ -529,12 +529,12 @@ class AbTestTest < ActionController::TestCase
529
529
  identify { "6e98ec" }
530
530
  metrics :coolness
531
531
  end
532
- assert_equal nil, experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, "6e98ec")
532
+ assert_nil experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, "6e98ec")
533
533
  assert id = experiment(:foobar).choose.id
534
534
  assert_equal id, experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, "6e98ec")
535
535
  end
536
536
 
537
- def test_ab_assigned
537
+ def test_ab_assigned_object
538
538
  identity = { :a => :b }
539
539
  new_ab_test :foobar do
540
540
  alternatives "foo", "bar"
@@ -542,7 +542,7 @@ class AbTestTest < ActionController::TestCase
542
542
  identify { identity }
543
543
  metrics :coolness
544
544
  end
545
- assert_equal nil, experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, identity)
545
+ assert_nil experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, identity)
546
546
  assert id = experiment(:foobar).choose.id
547
547
  assert_equal id, experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, identity)
548
548
  end
@@ -138,7 +138,8 @@ class UseVanityControllerTest < ActionController::TestCase
138
138
  end
139
139
 
140
140
  def test_vanity_identity_set_with_identity_paramater
141
- get :index, :_identity => "id_from_params"
141
+ params = { :_identity => "id_from_params" }
142
+ get :index, params
142
143
  assert_equal "id_from_params", @controller.send(:vanity_identity)
143
144
  end
144
145
 
@@ -156,7 +157,8 @@ class UseVanityControllerTest < ActionController::TestCase
156
157
 
157
158
  def test_vanity_identity_prefers_parameter_over_cookie
158
159
  @request.cookies['vanity_id'] = "old_id"
159
- get :index, :_identity => "id_from_params"
160
+ params = { :_identity => "id_from_params" }
161
+ get :index, params
160
162
  assert_equal "id_from_params", @controller.send(:vanity_identity)
161
163
  assert cookies['vanity_id'], "id_from_params"
162
164
  end
@@ -171,7 +173,8 @@ class UseVanityControllerTest < ActionController::TestCase
171
173
  # query parameter filter
172
174
 
173
175
  def test_redirects_and_loses_vanity_query_parameter
174
- get :index, :foo=>"bar", :_vanity=>"567"
176
+ params = { :foo=>"bar", :_vanity=>"567" }
177
+ get :index, params
175
178
  assert_redirected_to "/use_vanity?foo=bar"
176
179
  end
177
180
 
@@ -180,7 +183,8 @@ class UseVanityControllerTest < ActionController::TestCase
180
183
  fingerprint = experiment(:pie_or_cake).fingerprint(first)
181
184
  10.times do
182
185
  @controller = nil ; setup_controller_request_and_response
183
- get :index, :_vanity => fingerprint
186
+ params = { :_vanity => fingerprint }
187
+ get :index, params
184
188
  assert_equal experiment(:pie_or_cake).choose, experiment(:pie_or_cake).alternatives.first
185
189
  assert experiment(:pie_or_cake).showing?(first)
186
190
  end
@@ -190,13 +194,15 @@ class UseVanityControllerTest < ActionController::TestCase
190
194
  experiment(:pie_or_cake).chooses(experiment(:pie_or_cake).alternatives.last.value)
191
195
  first = experiment(:pie_or_cake).alternatives.first
192
196
  fingerprint = experiment(:pie_or_cake).fingerprint(first)
193
- post :index, :foo => "bar", :_vanity => fingerprint
197
+ params = { :foo => "bar", :_vanity => fingerprint }
198
+ post :index, params
194
199
  assert_response :success
195
200
  assert !experiment(:pie_or_cake).showing?(first)
196
201
  end
197
202
 
198
203
  def test_track_param_tracks_a_metric
199
- get :index, :_identity => "123", :_track => "sugar_high"
204
+ params = { :_identity => "123", :_track => "sugar_high" }
205
+ get :index, params
200
206
  assert_equal experiment(:pie_or_cake).alternatives[0].converted, 1
201
207
  end
202
208
 
@@ -19,7 +19,6 @@ class UseVanityMailerTest < ActionMailer::TestCase
19
19
  experiment(:pie_or_cake).chooses(:pie)
20
20
  VanityMailer.ab_test_subject(nil)
21
21
 
22
-
23
22
  alts = experiment(:pie_or_cake).alternatives
24
23
  assert_equal 1, alts.map(&:participants).sum
25
24
  end
@@ -234,7 +234,13 @@ describe Vanity::Metric::ActiveRecord do
234
234
  f.write <<-RUBY
235
235
  metric "Sky is limit" do
236
236
  model Sky, :conditions=>["height > 3"]
237
- Sky.after_save { |sky| Vanity.track!(:sky_is_limit) if sky.height_changed? && sky.height > 3 }
237
+ Sky.after_save do |sky|
238
+ if sky.respond_to?(:saved_change_to_height?) && sky.saved_change_to_height? && sky.height > 3
239
+ Vanity.track!(:sky_is_limit)
240
+ elsif sky.height_changed? && sky.height > 3
241
+ Vanity.track!(:sky_is_limit)
242
+ end
243
+ end
238
244
  end
239
245
  RUBY
240
246
  end
@@ -295,7 +301,7 @@ describe Vanity::Metric::ActiveRecord do
295
301
  File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
296
302
  f.write <<-RUBY
297
303
  metric "Sky is limit" do
298
- Sky.after_save { |sky| Vanity.track!(:sky_is_limit) if sky.height_changed? && sky.height > 3 }
304
+ Sky.after_save { |sky| Vanity.track!(:sky_is_limit) if sky.height && sky.height > 3 }
299
305
  end
300
306
  RUBY
301
307
  end
@@ -20,7 +20,7 @@ require "rails/test_help"
20
20
 
21
21
  require "vanity"
22
22
  require "timecop"
23
- require "mocha/mini_test"
23
+ require "mocha/minitest"
24
24
  require "fakefs/safe"
25
25
  require "webmock/minitest"
26
26
 
@@ -186,11 +186,53 @@ if defined?(ActiveSupport::TestCase)
186
186
 
187
187
  self.use_instantiated_fixtures = false if respond_to?(:use_instantiated_fixtures)
188
188
  self.use_transactional_fixtures = false if respond_to?(:use_transactional_fixtures)
189
+ self.use_transactional_tests = false if respond_to?(:use_transactional_tests)
190
+ end
191
+ end
192
+
193
+ # Shim for pre-5.0 tests
194
+ module LegacyTestRequests
195
+ def rails4?
196
+ ActiveRecord::VERSION::MAJOR <= 4
197
+ end
198
+
199
+ def get(path, params={}, headers={})
200
+ if rails4?
201
+ process(path, 'GET', params, headers)
202
+ else
203
+ process(path, method: 'GET', params: params, **headers)
204
+ end
205
+ end
206
+
207
+ def post(path, params={}, headers={})
208
+ if rails4?
209
+ process(path, 'POST', params, headers)
210
+ else
211
+ process(path, method: 'POST', params: params, **headers)
212
+ end
213
+ end
214
+
215
+ def put(path, params={}, headers={})
216
+ if rails4?
217
+ process(path, 'PUT', params, headers)
218
+ else
219
+ process(path, method: 'PUT', params: params, **headers)
220
+ end
221
+ end
222
+
223
+ def delete(path, params={}, headers={})
224
+ if rails4?
225
+ process(path, 'DELETE', params, headers)
226
+ else
227
+ process(path, method: 'DELETE', params: params, **headers)
228
+ end
189
229
  end
190
230
  end
191
231
 
192
232
  if defined?(ActionController::TestCase)
193
233
  class ActionController::TestCase
234
+ include LegacyTestRequests
235
+
194
236
  alias :setup_controller_request_and_response_without_vanity :setup_controller_request_and_response
195
237
  # Sets Vanity.context to the current controller, so you can do things like:
196
238
  # experiment(:simple).chooses(:green)
@@ -211,14 +253,19 @@ if ENV["DB"] == "active_record"
211
253
  ActiveRecord::Base.logger = $logger
212
254
 
213
255
  Vanity.connect!(VanityTestHelpers::DATABASE)
214
- require "generators/templates/vanity_migration"
215
- if defined?(ActiveRecord::Tasks)
216
- config = Vanity::Adapters::ActiveRecordAdapter::VanityRecord.connection_config
217
- ActiveRecord::Tasks::DatabaseTasks.drop(config.with_indifferent_access)
218
- else # Rails 3.2 fallback
219
- klasses = Vanity::Adapters::ActiveRecordAdapter::VanityRecord.descendants
220
- klasses.each { |k| k.connection.drop_table(k.table_name) if k.connection.table_exists?(k.table_name) }
221
- end
222
256
 
223
- VanityMigration.new.migrate(:up)
257
+ config = Vanity::Adapters::ActiveRecordAdapter::VanityRecord.connection_config
258
+ ActiveRecord::Tasks::DatabaseTasks.drop(config.with_indifferent_access)
259
+
260
+ # use generator to create the migration
261
+ require "rails/generators"
262
+ require "generators/vanity/migration_generator"
263
+ Rails::Generators.invoke "vanity"
264
+
265
+ migrate_path = File.expand_path("../dummy/db/migrate/", __FILE__)
266
+ if defined?(ActiveRecord::MigrationContext)
267
+ ActiveRecord::MigrationContext.new(migrate_path).migrate
268
+ else
269
+ ActiveRecord::Migrator.migrate(migrate_path)
270
+ end
224
271
  end
@@ -60,14 +60,16 @@ class RailsDashboardTest < ActionController::TestCase
60
60
  # -- Actions used in non-admin actions, e.g. in JS --
61
61
 
62
62
  def test_add_participant
63
- xhr :post, :add_participant, :v => 'food=0'
63
+ params = { :v => 'food=0' }
64
+ post :add_participant, params, :xhr => true
64
65
  assert_response :success
65
66
  assert @response.body.blank?
66
67
  assert_equal 1, experiment(:food).alternatives.map(&:participants).sum
67
68
  end
68
69
 
69
70
  def test_add_participant_multiple_experiments
70
- xhr :post, :add_participant, :v => 'food=0,drink=1'
71
+ params = { :v => 'food=0,drink=1' }
72
+ post :add_participant, params, :xhr => true
71
73
  assert_response :success
72
74
  assert @response.body.blank?
73
75
  assert_equal 1, experiment(:food).alternatives.map(&:participants).sum
@@ -76,18 +78,20 @@ class RailsDashboardTest < ActionController::TestCase
76
78
 
77
79
  def test_add_participant_with_invalid_request
78
80
  @request.user_agent = 'Googlebot/2.1 ( http://www.google.com/bot.html)'
79
- xhr :post, :add_participant, :v => 'food=0'
81
+ params = { :v => 'food=0' }
82
+ post :add_participant, params, :xhr => true
80
83
  assert_equal 0, experiment(:food).alternatives.map(&:participants).sum
81
84
  end
82
85
 
83
86
  def test_add_participant_no_params
84
- xhr :post, :add_participant
87
+ post :add_participant, :xhr => true
85
88
  assert_response :not_found
86
89
  assert @response.body.blank?
87
90
  end
88
91
 
89
92
  def test_add_participant_not_fail_for_unknown_experiment
90
- xhr :post, :add_participant, :e => 'unknown=0'
93
+ params = { :e => 'unknown=0' }
94
+ post :add_participant, params, :xhr => true
91
95
  assert_response :not_found
92
96
  assert @response.body.blank?
93
97
  end
@@ -96,13 +100,15 @@ class RailsDashboardTest < ActionController::TestCase
96
100
 
97
101
  def test_participant_renders_experiment_for_id
98
102
  experiment(:food).choose
99
- get :participant, :id => "1"
103
+ params = { :id => "1" }
104
+ get :participant, params
100
105
  assert_response :success
101
106
  assert @response.body =~ %r{id 1 is taking part in the following experiments:\n<ul class=\"experiments\">[\s]+<li class=\"experiment ab_test}
102
107
  end
103
108
 
104
109
  def test_participant_renders_empty_for_bad_id
105
- get :participant, :id => "2"
110
+ params = { :id => "2" }
111
+ get :participant, params
106
112
  assert_response :success
107
113
  assert @response.body =~ %r{<ul class=\"experiments\">[\s]+</ul>}
108
114
  end
@@ -114,19 +120,22 @@ class RailsDashboardTest < ActionController::TestCase
114
120
  end
115
121
 
116
122
  def test_complete_forces_confirmation
117
- xhr :post, :complete, :e => "food", :a => 0
123
+ params = { :e => "food", :a => 0 }
124
+ post :complete, params, :xhr => true
118
125
  assert_response :success
119
126
  assert @response.body =~ /#{ CGI.unescape({ :confirmed => 0 }.to_query) }/
120
127
  end
121
128
 
122
129
  def test_complete_with_confirmation_completes
123
- xhr :post, :complete, :e => "food", :a => 0, :confirmed => 'true'
130
+ params = { :e => "food", :a => 0, :confirmed => 'true' }
131
+ post :complete, params, :xhr => true
124
132
  assert_response :success
125
133
  assert !Vanity.playground.experiment(:food).active?
126
134
  end
127
135
 
128
136
  def test_chooses
129
- xhr :post, :chooses, :e => "food", :a => 0
137
+ params = { :e => "food", :a => 0 }
138
+ post :chooses, params, :xhr => true
130
139
  assert_response :success
131
140
  end
132
141
  end