vanity 2.2.6 → 2.2.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63e3e69d3a1d79cb7f36548e9b7c5a2ddf626b17
4
- data.tar.gz: 645fa9b2a710b2f0b7f631747bda160aed94c9eb
3
+ metadata.gz: 30ab64490eb076e3145a45d198b65778073979d6
4
+ data.tar.gz: 318343bcd0ba889d85cef2c4a3e8cf7b95ac8d78
5
5
  SHA512:
6
- metadata.gz: 1aba836ab77005465669a51695f8eb6fb01ac76841eea6cff3e5202b9c8275eb7777bf0c0dc1ff2c5f623d6e981bc161dcb8689b19f985f74a2a86bfe0c0b84e
7
- data.tar.gz: 8e134db0be9cc68e9effd93170fa931bf03787cdc57bb438842ee5399aab8f549f33ca90c7afbe462da7d50bf3c2dacc7ab6a01bbf3af6506d7b0848df05c3ed
6
+ metadata.gz: 08ac7419f74bb5282753cf8f5303de34eea8b6bb91c9c9a2483d40d9b23475864940db3f64f27887409ff7bb98676a0c685e0847a6064f068c4402e366fe222a
7
+ data.tar.gz: 044f52924d4c36026360db12f36585517267ddeb3149a0858a8204c906966252144c6a9c4360aa6a03aba5aabf41ee0710e021bc026c927bac1a0b1045714b71
data/.gitignore CHANGED
@@ -1,6 +1,6 @@
1
1
  vendor
2
2
  log/*
3
- test/dummy/db/*.sqlite3
3
+ test/dummy/**/*.sqlite3
4
4
  test/dummy/log/*.log
5
5
  test/dummy/tmp/
6
6
 
@@ -15,4 +15,4 @@ html
15
15
  .bundle
16
16
  .rvmrc
17
17
  .ruby-version
18
- .ruby-gemset
18
+ .ruby-gemset
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ == Unreleased
2
+
3
+ == 2.2.7 (2016-12-21)
4
+ * Fix 'base 17' typo, correct id bucketing (#308, @bazzargh)
5
+ * Update docs about `rebalance_frequency` (#310, @sdhull)
6
+ * Resolve Rails 5 deprecation warning for Mime::HTML (#313, @terracatta)
7
+ * Update MockAdapter and testing using it (#316, #309 @urbanautomaton)
8
+ * Deprecate calling #ab_seen with alternative instance (#317, @urbanautomaton)
9
+
1
10
  == 2.2.6 (2016-10-04)
2
11
  * Fixes for `Vanity.ab_test()` vs. view helper `ab_test` (@phillbaker)
3
12
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- vanity (2.2.6)
4
+ vanity (2.2.7)
5
5
  i18n
6
6
 
7
7
  GEM
@@ -162,9 +162,14 @@ ab_test "noodle_test" do
162
162
  alternatives "spaghetti", "linguine"
163
163
  metrics :signup
164
164
  score_method :bayes_bandit_score
165
+ rebalance_frequency 100
165
166
  end
166
167
  </pre>
167
168
 
169
+ Note: Setting the score method to `bayes_bandit_score` won't adjust alternative probabilities (ie, you won't get the benefit of maximizing conversions) unless you also set `rebalance_frequency`, which controls how many impressions must pass before rebalancing alternative probabilities.
170
+
171
+ Also note that impressions count is stored in-memory and is therefore reset upon restart of your app.
172
+
168
173
  h3(#test). A/B Testing and Code Testing
169
174
 
170
175
  If you're presenting more than one alternative to visitors of your site, you'll want to test more than one alternative. Don't let A/B testing become A/broken.
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: ..
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: .././
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: ..
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: ../
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: ../
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -142,6 +142,16 @@ module Vanity
142
142
  fail "Not implemented"
143
143
  end
144
144
 
145
+ private
146
+
147
+ def with_ab_seen_deprecation(experiment, identity, alternative)
148
+ if alternative.respond_to?(:id)
149
+ Vanity.configuration.logger.warn(%q{Deprecated: #ab_seen should be passed the alternative id, not an Alternative instance})
150
+ yield experiment, identity, alternative.id
151
+ else
152
+ yield experiment, identity, alternative
153
+ end
154
+ end
145
155
  end
146
156
  end
147
157
  end
@@ -255,9 +255,11 @@ module Vanity
255
255
  end
256
256
 
257
257
  # Determines if a participant already has seen this alternative in this experiment.
258
- def ab_seen(experiment, identity, alternative)
259
- participant = VanityParticipant.retrieve(experiment, identity, false)
260
- participant && participant.seen == alternative.id
258
+ def ab_seen(experiment, identity, alternative_or_id)
259
+ with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
260
+ participant = VanityParticipant.retrieve(expt, ident, false)
261
+ participant && participant.seen == alt_id
262
+ end
261
263
  end
262
264
 
263
265
  # Returns the participant's seen alternative in this experiment, if it exists
@@ -273,7 +275,7 @@ module Vanity
273
275
  # previously recorded as participating in this experiment.
274
276
  def ab_add_conversion(experiment, alternative, identity, count = 1, implicit = false)
275
277
  participant = VanityParticipant.retrieve(experiment, identity, false)
276
- VanityParticipant.retrieve(experiment, identity, implicit, :converted => alternative)
278
+ VanityParticipant.retrieve(experiment, identity, implicit, :converted => alternative, :seen => alternative)
277
279
  VanityExperiment.retrieve(experiment).increment_conversion(alternative, count)
278
280
  end
279
281
 
@@ -111,9 +111,7 @@ module Vanity
111
111
  end
112
112
 
113
113
  def ab_counts(experiment, alternative)
114
- @experiments[experiment] ||= {}
115
- @experiments[experiment][:alternatives] ||= {}
116
- alt = @experiments[experiment][:alternatives][alternative] ||= {}
114
+ alt = alternative(experiment, alternative)
117
115
  { :participants => alt[:participants] ? alt[:participants].size : 0,
118
116
  :converted => alt[:converted] ? alt[:converted].size : 0,
119
117
  :conversions => alt[:conversions] || 0 }
@@ -134,17 +132,29 @@ module Vanity
134
132
  end
135
133
 
136
134
  def ab_add_participant(experiment, alternative, identity)
137
- @experiments[experiment] ||= {}
138
- @experiments[experiment][:alternatives] ||= {}
139
- alt = @experiments[experiment][:alternatives][alternative] ||= {}
135
+ alt = alternative(experiment, alternative)
140
136
  alt[:participants] ||= Set.new
141
137
  alt[:participants] << identity
142
138
  end
143
139
 
140
+ def ab_seen(experiment, identity, alternative_or_id)
141
+ with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
142
+ if ab_assigned(expt, ident) == alt_id
143
+ alt_id
144
+ end
145
+ end
146
+ end
147
+
148
+ def ab_assigned(experiment, identity)
149
+ alternatives_for(experiment).each do |alt_id, alt_state|
150
+ return alt_id if alt_state[:participants].include?(identity)
151
+ end
152
+
153
+ nil
154
+ end
155
+
144
156
  def ab_add_conversion(experiment, alternative, identity, count = 1, implicit = false)
145
- @experiments[experiment] ||= {}
146
- @experiments[experiment][:alternatives] ||= {}
147
- alt = @experiments[experiment][:alternatives][alternative] ||= {}
157
+ alt = alternative(experiment, alternative)
148
158
  alt[:participants] ||= Set.new
149
159
  alt[:converted] ||= Set.new
150
160
  alt[:conversions] ||= 0
@@ -170,6 +180,20 @@ module Vanity
170
180
  def destroy_experiment(experiment)
171
181
  @experiments.delete experiment
172
182
  end
183
+
184
+ private
185
+
186
+ def alternative(experiment, alternative)
187
+ alternatives_for(experiment)[alternative] ||= {}
188
+ alternatives_for(experiment)[alternative]
189
+ end
190
+
191
+ def alternatives_for(experiment)
192
+ @experiments[experiment] ||= {}
193
+ @experiments[experiment][:alternatives] ||= {}
194
+
195
+ @experiments[experiment][:alternatives]
196
+ end
173
197
  end
174
198
  end
175
199
  end
@@ -200,9 +200,11 @@ module Vanity
200
200
  end
201
201
 
202
202
  # Determines if a participant already has seen this alternative in this experiment.
203
- def ab_seen(experiment, identity, alternative)
204
- participant = @participants.find(:experiment=>experiment, :identity=>identity).limit(1).projection(:seen=>1).first
205
- participant && participant["seen"].first == alternative.id
203
+ def ab_seen(experiment, identity, alternative_or_id)
204
+ with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
205
+ participant = @participants.find(:experiment=>expt, :identity=>ident).limit(1).projection(:seen=>1).first
206
+ participant && participant["seen"].first == alt_id
207
+ end
206
208
  end
207
209
 
208
210
  # Returns the participant's seen alternative in this experiment, if it exists
@@ -87,7 +87,7 @@ module Vanity
87
87
 
88
88
  def metric_values(metric, from, to)
89
89
  single = @metrics.mget(*(from.to_date..to.to_date).map { |date| "#{metric}:#{date}:value:0" }) || []
90
- single.map { |v| [v] }
90
+ single.map { |v| [v.to_i] }
91
91
  end
92
92
 
93
93
  def destroy_metric(metric)
@@ -175,12 +175,14 @@ module Vanity
175
175
  end
176
176
  end
177
177
 
178
- def ab_seen(experiment, identity, alternative)
179
- call_redis_with_failover(experiment, identity, alternative) do
180
- if @experiments.sismember "#{experiment}:alts:#{alternative.id}:participants", identity
181
- alternative
182
- else
183
- nil
178
+ def ab_seen(experiment, identity, alternative_or_id)
179
+ with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
180
+ call_redis_with_failover(expt, ident, alt_id) do
181
+ if @experiments.sismember "#{expt}:alts:#{alt_id}:participants", ident
182
+ alt_id
183
+ else
184
+ nil
185
+ end
184
186
  end
185
187
  end
186
188
  end
@@ -31,7 +31,7 @@ module Vanity
31
31
  # )
32
32
  # @since 2.0.0
33
33
  def initialize(specification=nil)
34
- @specification = specification || DEFAULT_SPECIFICATION
34
+ @specification = Specification.new(specification || DEFAULT_SPECIFICATION).to_h
35
35
 
36
36
  if Autoconnect.playground_should_autoconnect?
37
37
  @adapter = setup_connection(@specification)
@@ -54,47 +54,59 @@ module Vanity
54
54
 
55
55
  private
56
56
 
57
- def setup_connection(spec)
58
- case spec
59
- when String
60
- spec_hash = build_specification_hash_from_url(spec)
61
- establish_connection(spec_hash)
62
- when Hash
63
- validate_specification_hash(spec)
64
- if spec[:redis]
65
- establish_connection(
66
- adapter: :redis,
67
- redis: spec[:redis]
68
- )
57
+ def setup_connection(specification)
58
+ establish_connection(specification)
59
+ end
60
+
61
+ def establish_connection(spec)
62
+ Adapters.establish_connection(spec)
63
+ end
64
+
65
+ class Specification
66
+ def initialize(spec)
67
+ case spec
68
+ when String
69
+ @spec = build_specification_hash_from_url(spec)
70
+ when Hash
71
+ if spec[:redis]
72
+ @spec = {
73
+ adapter: :redis,
74
+ redis: spec[:redis]
75
+ }
76
+ else
77
+ @spec = spec
78
+ end
69
79
  else
70
- establish_connection(spec)
80
+ raise InvalidSpecification.new("Unsupported connection specification: #{spec.inspect}")
71
81
  end
72
- else
73
- raise InvalidSpecification.new("Unsupported connection specification: #{spec.inspect}")
82
+
83
+ validate_specification_hash(@spec)
74
84
  end
75
- end
76
85
 
77
- def build_specification_hash_from_url(connection_url)
78
- uri = URI.parse(connection_url)
79
- params = CGI.parse(uri.query) if uri.query
80
- {
81
- adapter: uri.scheme,
82
- username: uri.user,
83
- password: uri.password,
84
- host: uri.host,
85
- port: uri.port,
86
- path: uri.path,
87
- params: params
88
- }
89
- end
86
+ def to_h
87
+ @spec
88
+ end
90
89
 
91
- def validate_specification_hash(spec)
92
- all_symbol_keys = spec.keys.all? { |key| key.is_a?(Symbol) }
93
- raise InvalidSpecification unless all_symbol_keys
94
- end
90
+ private
95
91
 
96
- def establish_connection(spec)
97
- Adapters.establish_connection(spec)
92
+ def validate_specification_hash(spec)
93
+ all_symbol_keys = spec.keys.all? { |key| key.is_a?(Symbol) }
94
+ raise InvalidSpecification unless all_symbol_keys
95
+ end
96
+
97
+ def build_specification_hash_from_url(connection_url)
98
+ uri = URI.parse(connection_url)
99
+ params = CGI.parse(uri.query) if uri.query
100
+ {
101
+ adapter: uri.scheme,
102
+ username: uri.user,
103
+ password: uri.password,
104
+ host: uri.host,
105
+ port: uri.port,
106
+ path: uri.path,
107
+ params: params
108
+ }
109
+ end
98
110
  end
99
111
  end
100
- end
112
+ end
@@ -610,7 +610,7 @@ module Vanity
610
610
  end
611
611
  end
612
612
 
613
- Digest::MD5.hexdigest("#{name}/#{identity}").to_i(17) % @alternatives.size
613
+ Digest::MD5.hexdigest("#{name}/#{identity}").to_i(16) % @alternatives.size
614
614
  end
615
615
 
616
616
  # Saves the assignment of an alternative to a person and performs the
@@ -635,7 +635,7 @@ module Vanity
635
635
  # if we have an on_assignment block, call it on new assignments
636
636
  if defined?(@on_assignment_block) && @on_assignment_block
637
637
  assignment = alternatives[index]
638
- if !connection.ab_seen @id, identity, assignment
638
+ if !connection.ab_seen @id, identity, index
639
639
  @on_assignment_block.call(Vanity.context, identity, assignment, self)
640
640
  end
641
641
  end
@@ -348,7 +348,7 @@ module Vanity
348
348
  # Step 3: Open your browser to http://localhost:3000/vanity
349
349
  module Dashboard
350
350
  def index
351
- render :file=>Vanity.template("_report"),:content_type=>Mime::HTML, :locals=>{
351
+ render :file=>Vanity.template("_report"),:content_type=>Mime[:html], :locals=>{
352
352
  :experiments=>Vanity.playground.experiments,
353
353
  :experiments_persisted=>Vanity.playground.experiments_persisted?,
354
354
  :metrics=>Vanity.playground.metrics
@@ -356,7 +356,7 @@ module Vanity
356
356
  end
357
357
 
358
358
  def participant
359
- render :file=>Vanity.template("_participant"), :locals=>{:participant_id => params[:id], :participant_info => Vanity.playground.participant_info(params[:id])}, :content_type=>Mime::HTML
359
+ render :file=>Vanity.template("_participant"), :locals=>{:participant_id => params[:id], :participant_info => Vanity.playground.participant_info(params[:id])}, :content_type=>Mime[:html]
360
360
  end
361
361
 
362
362
  def complete
@@ -1,5 +1,5 @@
1
1
  module Vanity
2
- VERSION = "2.2.6"
2
+ VERSION = "2.2.7"
3
3
 
4
4
  module Version
5
5
  version = VERSION.to_s.split(".").map { |i| i.to_i }
@@ -0,0 +1,19 @@
1
+ require "test_helper"
2
+ require 'vanity/adapters/active_record_adapter'
3
+ require 'adapters/shared_tests'
4
+
5
+ describe Vanity::Adapters::ActiveRecordAdapter do
6
+
7
+ def specification
8
+ Vanity::Connection::Specification.new(VanityTestHelpers::DATABASE_OPTIONS["active_record"])
9
+ end
10
+
11
+ def adapter
12
+ Vanity::Adapters::ActiveRecordAdapter.new(specification.to_h)
13
+ end
14
+
15
+ if ENV["DB"] == "active_record"
16
+ include Vanity::Adapters::SharedTests
17
+ end
18
+
19
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+ require 'adapters/shared_tests'
3
+
4
+ describe Vanity::Adapters::MockAdapter do
5
+
6
+ def specification
7
+ Vanity::Connection::Specification.new(VanityTestHelpers::DATABASE_OPTIONS["mock"])
8
+ end
9
+
10
+ def adapter
11
+ Vanity::Adapters::MockAdapter.new({})
12
+ end
13
+
14
+ include Vanity::Adapters::SharedTests
15
+
16
+ end
@@ -0,0 +1,19 @@
1
+ require "test_helper"
2
+ require 'vanity/adapters/mongodb_adapter'
3
+ require 'adapters/shared_tests'
4
+
5
+ describe Vanity::Adapters::MongodbAdapter do
6
+
7
+ def specification
8
+ Vanity::Connection::Specification.new(VanityTestHelpers::DATABASE_OPTIONS["mongodb"])
9
+ end
10
+
11
+ def adapter
12
+ Vanity::Adapters::MongodbAdapter.new(specification.to_h)
13
+ end
14
+
15
+ if ENV["DB"] == "mongodb"
16
+ include Vanity::Adapters::SharedTests
17
+ end
18
+
19
+ end
@@ -1,4 +1,6 @@
1
1
  require "test_helper"
2
+ require 'vanity/adapters/redis_adapter'
3
+ require 'adapters/shared_tests'
2
4
 
3
5
  describe Vanity::Adapters::RedisAdapter do
4
6
  before do
@@ -6,6 +8,16 @@ describe Vanity::Adapters::RedisAdapter do
6
8
  require "redis/namespace"
7
9
  end
8
10
 
11
+ def specification
12
+ Vanity::Connection::Specification.new(VanityTestHelpers::DATABASE_OPTIONS["redis"])
13
+ end
14
+
15
+ def adapter
16
+ Vanity::Adapters::RedisAdapter.new(specification.to_h)
17
+ end
18
+
19
+ include Vanity::Adapters::SharedTests
20
+
9
21
  it "warns on disconnect error" do
10
22
  if defined?(Redis)
11
23
  assert_silent do
@@ -0,0 +1,336 @@
1
+ require 'time'
2
+
3
+ module Vanity::Adapters::SharedTests
4
+
5
+ DummyAlternative = Struct.new(:id)
6
+
7
+ def identity
8
+ 'test-identity'
9
+ end
10
+
11
+ def experiment
12
+ :experiment
13
+ end
14
+
15
+ def alternative
16
+ 1
17
+ end
18
+
19
+ def metric_name
20
+ :purchases
21
+ end
22
+
23
+ def run_experiment
24
+ @subject.ab_add_participant(experiment, alternative, identity)
25
+ @subject.ab_add_participant(experiment, alternative, 'other-identity')
26
+ @subject.ab_add_conversion(experiment, alternative, identity, 2)
27
+ end
28
+
29
+ def self.included(base)
30
+ base.instance_eval do
31
+ before do
32
+ @subject = adapter
33
+ @subject.flushdb
34
+
35
+ metric "purchases"
36
+
37
+ new_ab_test "experiment" do
38
+ alternatives :control, :test
39
+ default :control
40
+ metrics :purchases
41
+ end
42
+ end
43
+
44
+ describe 'metrics methods' do
45
+ describe '#get_metric_last_update_at' do
46
+ it 'is nil if the metric has never been tracked' do
47
+ refute(@subject.get_metric_last_update_at(metric_name))
48
+ end
49
+
50
+ it 'returns the time of the last tracked event if present' do
51
+ time = Time.now
52
+ @subject.metric_track(metric_name, time, identity, [1])
53
+
54
+ assert_in_delta(
55
+ time,
56
+ @subject.get_metric_last_update_at(metric_name),
57
+ 1.0
58
+ )
59
+ end
60
+ end
61
+
62
+ describe '#metric_values' do
63
+ it 'returns the tracked metrics from the given range, binned by date' do
64
+ time_1 = Time.iso8601("2016-01-01T00:00:00+00:00")
65
+ time_2 = Time.iso8601("2016-01-01T12:00:00+00:00")
66
+ time_3 = Time.iso8601("2016-01-02T00:00:00+00:00")
67
+ time_4 = Time.iso8601("2016-01-02T12:00:00+00:00")
68
+ time_5 = Time.iso8601("2016-01-03T12:00:00+00:00")
69
+
70
+ @subject.metric_track(metric_name, time_1, identity, [1])
71
+ @subject.metric_track(metric_name, time_2, identity, [2])
72
+ @subject.metric_track(metric_name, time_3, identity, [4])
73
+ @subject.metric_track(metric_name, time_4, identity, [8])
74
+ @subject.metric_track(metric_name, time_5, identity, [16])
75
+
76
+ assert_equal(
77
+ [[3], [12]],
78
+ @subject.metric_values(metric_name, time_1, time_4)
79
+ )
80
+ end
81
+ end
82
+
83
+ describe '#destroy_metric' do
84
+ it 'removes all data related to the metric' do
85
+ @subject.metric_track(metric_name, Time.now, identity, [1])
86
+
87
+ @subject.destroy_metric(metric_name)
88
+
89
+ refute(@subject.get_metric_last_update_at(metric_name))
90
+ end
91
+ end
92
+ end
93
+
94
+ describe 'generic experiment methods' do
95
+ describe '#experiment_persisted?' do
96
+ it 'returns false if the experiment is unknown' do
97
+ refute(@subject.experiment_persisted?("other_experiment"))
98
+ end
99
+
100
+ it 'returns true if the experiment has been created' do
101
+ @subject.set_experiment_created_at("other_experiment", Time.now)
102
+
103
+ assert(@subject.experiment_persisted?("other_experiment"))
104
+ end
105
+ end
106
+
107
+ describe '#set_experiment_created_at' do
108
+ it 'sets the experiment creation date' do
109
+ time = Time.now
110
+ @subject.set_experiment_created_at(experiment, time)
111
+
112
+ assert_in_delta(
113
+ time,
114
+ @subject.get_experiment_created_at(experiment),
115
+ 1.0
116
+ )
117
+ end
118
+ end
119
+
120
+ describe '#destroy_experiment' do
121
+ it 'removes all information about the experiment' do
122
+ run_experiment
123
+ @subject.destroy_experiment(experiment)
124
+
125
+ refute(@subject.experiment_persisted?(experiment))
126
+ end
127
+ end
128
+
129
+ describe '#is_experiment_enabled?' do
130
+ def with_experiments_start_enabled(enabled)
131
+ begin
132
+ original_value = Vanity.configuration.experiments_start_enabled
133
+ Vanity.configuration.experiments_start_enabled = enabled
134
+ yield
135
+ ensure
136
+ Vanity.configuration.experiments_start_enabled = original_value
137
+ end
138
+ end
139
+
140
+ describe 'when experiments start enabled' do
141
+ it 'is true when the enabled value is unset' do
142
+ with_experiments_start_enabled(true) do
143
+ assert(@subject.is_experiment_enabled?("other_experiment"))
144
+ end
145
+ end
146
+
147
+ it 'is false when the enabled value is set to false' do
148
+ with_experiments_start_enabled(true) do
149
+ @subject.set_experiment_enabled("other_experiment", false)
150
+
151
+ refute(@subject.is_experiment_enabled?("other_experiment"))
152
+ end
153
+ end
154
+ end
155
+
156
+ describe 'when experiments do not start enabled' do
157
+ it 'is false when the enabled value is unset' do
158
+ with_experiments_start_enabled(false) do
159
+ refute(@subject.is_experiment_enabled?("other_experiment"))
160
+ end
161
+ end
162
+
163
+ it 'is true when the enabled value is set to true' do
164
+ with_experiments_start_enabled(false) do
165
+ @subject.set_experiment_enabled("other_experiment", true)
166
+
167
+ assert(@subject.is_experiment_enabled?("other_experiment"))
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ describe '#is_experiment_completed?' do
174
+ it 'is true if the completion date is set' do
175
+ @subject.set_experiment_completed_at(experiment, Time.now)
176
+
177
+ assert(@subject.is_experiment_completed?(experiment))
178
+ end
179
+
180
+ it 'is false if the completion date is unset' do
181
+ refute(@subject.is_experiment_completed?(experiment))
182
+ end
183
+ end
184
+ end
185
+
186
+ describe 'A/B test methods' do
187
+ describe '#ab_add_conversion' do
188
+ it 'adds the participant and the conversion when implicit=true' do
189
+ @subject.ab_add_conversion(experiment, alternative, identity, 1, true)
190
+
191
+ assert_equal(
192
+ {:participants => 1, :conversions => 1, :converted => 1},
193
+ @subject.ab_counts(experiment, alternative)
194
+ )
195
+ end
196
+ end
197
+
198
+ describe '#ab_add_participant' do
199
+ it 'adds the participant to the specified alternative' do
200
+ @subject.ab_add_participant(experiment, alternative, identity)
201
+
202
+ assert_equal(
203
+ {:participants => 1, :conversions => 0, :converted => 0},
204
+ @subject.ab_counts(experiment, alternative)
205
+ )
206
+ end
207
+ end
208
+
209
+ describe '#ab_seen' do
210
+ describe 'called with an Alternative instance' do
211
+ def capture_logs
212
+ require 'stringio'
213
+ original_logger = Vanity.configuration.logger
214
+ log_output = StringIO.new
215
+ Vanity.configuration.logger = Logger.new(log_output)
216
+ yield
217
+ log_output.string
218
+ ensure
219
+ Vanity.configuration.logger = original_logger
220
+ end
221
+
222
+ before do
223
+ @alternative_instance = DummyAlternative.new(alternative)
224
+ end
225
+
226
+ it 'emits a deprecation warning' do
227
+ @subject.ab_add_participant(experiment, alternative, identity)
228
+
229
+ out = capture_logs do
230
+ @subject.ab_seen(experiment, identity, @alternative_instance)
231
+ end
232
+
233
+ assert_match(/Deprecated/, out)
234
+ end
235
+
236
+ it 'returns a truthy value if the identity is assigned to the alternative' do
237
+ @subject.ab_add_participant(experiment, alternative, identity)
238
+
239
+ assert(@subject.ab_seen(experiment, identity, @alternative_instance))
240
+ end
241
+
242
+ it 'returns a falsey value if the identity is not assigned to the alternative' do
243
+ @subject.ab_add_participant(experiment, alternative, identity)
244
+
245
+ refute(@subject.ab_seen(experiment, identity, DummyAlternative.new(2)))
246
+ end
247
+ end
248
+
249
+ describe 'called with an alternative id' do
250
+ it 'returns a truthy value if the identity is assigned to the alternative' do
251
+ @subject.ab_add_participant(experiment, alternative, identity)
252
+
253
+ assert(@subject.ab_seen(experiment, identity, alternative))
254
+ end
255
+
256
+ it 'returns a falsey value if the identity is not assigned to the alternative' do
257
+ @subject.ab_add_participant(experiment, alternative, identity)
258
+
259
+ refute(@subject.ab_seen(experiment, identity, 2))
260
+ end
261
+ end
262
+ end
263
+
264
+ describe '#ab_assigned' do
265
+ it 'returns the assigned alternative if present' do
266
+ @subject.ab_add_participant(experiment, alternative, identity)
267
+
268
+ assert_equal(
269
+ alternative,
270
+ @subject.ab_assigned(experiment, identity)
271
+ )
272
+ end
273
+
274
+ it 'returns nil if the identity has no assignment' do
275
+ assert_equal(
276
+ nil,
277
+ @subject.ab_assigned(experiment, identity)
278
+ )
279
+ end
280
+ end
281
+
282
+ describe '#ab_counts' do
283
+ it 'returns the counts of participants, conversions and converted for the alternative' do
284
+ run_experiment
285
+
286
+ assert_equal(
287
+ {:participants => 2, :conversions => 2, :converted => 1},
288
+ @subject.ab_counts(experiment, alternative)
289
+ )
290
+ end
291
+ end
292
+
293
+ describe '#ab_get_outcome' do
294
+ it 'returns the outcome if one is set' do
295
+ @subject.ab_set_outcome(experiment, alternative)
296
+
297
+ assert_equal(
298
+ alternative,
299
+ @subject.ab_get_outcome(experiment)
300
+ )
301
+ end
302
+
303
+ it 'returns nil otherwise' do
304
+ assert_equal(
305
+ nil,
306
+ @subject.ab_get_outcome(experiment)
307
+ )
308
+ end
309
+ end
310
+
311
+ describe '#ab_show' do
312
+ it 'forces an alternative to be shown to the given identity' do
313
+ @subject.ab_show(experiment, identity, alternative)
314
+
315
+ assert_equal(
316
+ alternative,
317
+ @subject.ab_showing(experiment, identity)
318
+ )
319
+ end
320
+ end
321
+
322
+ describe '#ab_not_showing' do
323
+ it 'cancels a previously-set showing alternative' do
324
+ @subject.ab_show(experiment, identity, alternative)
325
+ @subject.ab_not_showing(experiment, identity)
326
+
327
+ assert_equal(
328
+ nil,
329
+ @subject.ab_showing(experiment, identity)
330
+ )
331
+ end
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
@@ -43,12 +43,14 @@ module VanityTestHelpers
43
43
  # We go destructive on the database at the end of each run, so make sure we
44
44
  # don't use databases you care about. For Redis, we pick database 15
45
45
  # (default is 0).
46
- DATABASE = {
46
+ DATABASE_OPTIONS = {
47
47
  "redis" => "redis://localhost/15",
48
48
  "mongodb" => "mongodb://localhost/vanity",
49
49
  "active_record" => { :adapter => "active_record", :active_record_adapter =>"default" },
50
50
  "mock" => "mock:/"
51
- }[ENV["DB"]] or raise "No support yet for #{ENV["DB"]}"
51
+ }
52
+
53
+ DATABASE = DATABASE_OPTIONS[ENV["DB"]] or raise "No support yet for #{ENV["DB"]}"
52
54
 
53
55
  TEST_DATA_FILES = Dir[File.expand_path("../data/*", __FILE__)]
54
56
  VANITY_CONFIGS = TEST_DATA_FILES.each.with_object({}) do |path, hash|
@@ -108,12 +108,16 @@ describe Vanity do
108
108
  f.write(connection_config)
109
109
  end
110
110
 
111
- Vanity.reset!
112
- Vanity.disconnect!
113
- Vanity::Connection.stubs(:new)
114
- Vanity.connect!
111
+ out, _err = capture_io do
112
+ Vanity.reset!
113
+ Vanity.configuration.logger = Logger.new($stdout)
114
+ Vanity.disconnect!
115
+ Vanity::Connection.stubs(:new)
116
+ Vanity.connect!
117
+ end
115
118
 
116
119
  assert_equal false, Vanity.configuration.collecting
120
+ assert_match(/Deprecated/, out)
117
121
  end
118
122
  end
119
123
  end
@@ -178,4 +182,4 @@ describe Vanity do
178
182
  refute_same original_configuration, Vanity.reload!
179
183
  end
180
184
  end
181
- end
185
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vanity
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.6
4
+ version: 2.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Assaf Arkin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-04 00:00:00.000000000 Z
11
+ date: 2016-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -224,7 +224,11 @@ files:
224
224
  - lib/vanity/templates/vanity.js
225
225
  - lib/vanity/vanity.rb
226
226
  - lib/vanity/version.rb
227
+ - test/adapters/active_record_adapter_test.rb
228
+ - test/adapters/mock_adapter_test.rb
229
+ - test/adapters/mongodb_adapter_test.rb
227
230
  - test/adapters/redis_adapter_test.rb
231
+ - test/adapters/shared_tests.rb
228
232
  - test/autoconnect_test.rb
229
233
  - test/cli_test.rb
230
234
  - test/commands/report_test.rb
@@ -286,7 +290,7 @@ metadata: {}
286
290
  post_install_message: To get started run vanity --help
287
291
  rdoc_options:
288
292
  - "--title"
289
- - Vanity 2.2.6
293
+ - Vanity 2.2.7
290
294
  - "--main"
291
295
  - README.md
292
296
  - "--webcvs"
@@ -310,7 +314,11 @@ signing_key:
310
314
  specification_version: 4
311
315
  summary: Experience Driven Development framework for Ruby
312
316
  test_files:
317
+ - test/adapters/active_record_adapter_test.rb
318
+ - test/adapters/mock_adapter_test.rb
319
+ - test/adapters/mongodb_adapter_test.rb
313
320
  - test/adapters/redis_adapter_test.rb
321
+ - test/adapters/shared_tests.rb
314
322
  - test/autoconnect_test.rb
315
323
  - test/cli_test.rb
316
324
  - test/commands/report_test.rb