abongo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ class Abongo
2
+ module Controller
3
+ module Dashboard
4
+
5
+ if Rails::VERSION::MAJOR <= 2
6
+ ActionController::Base.view_paths.unshift File.join(File.dirname(__FILE__), "../views")
7
+ else
8
+ ActionController::Base.prepend_view_path File.join(File.dirname(__FILE__), "../views")
9
+ end
10
+
11
+ def index
12
+ @experiments = Abongo.all_tests.map{|e| {'participants' => 0, 'conversions' => 0}.merge(e)}
13
+ render :template => 'dashboard/index'
14
+ end
15
+
16
+ def end_experiment
17
+ @alternative = Abongo.get_alternative(params[:id])
18
+ @experiment = Abongo.get_test(@alternative['test'])
19
+ Abongo.end_experiment! @experiment['name'], @alternative['content']
20
+ redirect_to :back
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,93 @@
1
+ module Abongo::Statistics
2
+
3
+ HANDY_Z_SCORE_CHEATSHEET = [[0.10, 1.29], [0.05, 1.65], [0.01, 2.33], [0.001, 3.08]]
4
+
5
+ PERCENTAGES = {0.10 => '90%', 0.05 => '95%', 0.01 => '99%', 0.001 => '99.9%'}
6
+
7
+ DESCRIPTION_IN_WORDS = {0.10 => 'fairly confident', 0.05 => 'confident',
8
+ 0.01 => 'very confident', 0.001 => 'extremely confident'}
9
+
10
+ def self.zscore(alternatives)
11
+ if alternatives.size != 2
12
+ raise "Sorry, can't currently automatically calculate statistics for A/B tests with > 2 alternatives."
13
+ end
14
+
15
+ if (alternatives[0]['participants'] == 0) || (alternatives[1]['participants'] == 0)
16
+ raise "Can't calculate the z score if either of the alternatives lacks participants."
17
+ end
18
+
19
+ cr1 = conversion_rate(alternatives[0])
20
+ cr2 = conversion_rate(alternatives[1])
21
+
22
+ n1 = alternatives[0]['participants']
23
+ n2 = alternatives[1]['participants']
24
+
25
+ numerator = cr1 - cr2
26
+ frac1 = cr1 * (1 - cr1) / n1
27
+ frac2 = cr2 * (1 - cr2) / n2
28
+
29
+ numerator / ((frac1 + frac2) ** 0.5)
30
+ end
31
+
32
+ def self.p_value(alternatives)
33
+ index = 0
34
+ z = zscore(alternatives)
35
+ z = z.abs
36
+ found_p = nil
37
+ while index < HANDY_Z_SCORE_CHEATSHEET.size do
38
+ if (z > HANDY_Z_SCORE_CHEATSHEET[index][1])
39
+ found_p = HANDY_Z_SCORE_CHEATSHEET[index][0]
40
+ end
41
+ index += 1
42
+ end
43
+ found_p
44
+ end
45
+
46
+ def self.is_statistically_significant?(p = 0.05)
47
+ p_value <= p
48
+ end
49
+
50
+ def self.conversion_rate(exp)
51
+ 1.0 * exp['conversions'] / exp['participants']
52
+ end
53
+
54
+ def self.pretty_conversion_rate(exp)
55
+ sprintf("%4.2f%%", conversion_rate(exp) * 100)
56
+ end
57
+
58
+ def self.describe_result_in_words(experiment, alternatives)
59
+ begin
60
+ z = zscore(alternatives)
61
+ rescue
62
+ return "Could not execute the significance test because one or more of the alternatives has not been seen yet."
63
+ end
64
+ p = p_value(alternatives)
65
+
66
+ words = ""
67
+ if (alternatives[0]['participants'] < 10) || (alternatives[1]['participants'] < 10)
68
+ words += "Take these results with a grain of salt since your samples are so small: "
69
+ end
70
+
71
+ best_alternative = alternatives.max{|a, b| conversion_rate(a) <=> conversion_rate(b)}
72
+ alts = alternatives - [best_alternative]
73
+ worst_alternative = alts.first
74
+
75
+ words += "The best alternative you have is: [#{best_alternative['content']}], which had "
76
+ words += "#{best_alternative['conversions']} conversions from #{best_alternative['participants']} participants "
77
+ words += "(#{pretty_conversion_rate(best_alternative)}). The other alternative was [#{worst_alternative['content']}], "
78
+ words += "which had #{worst_alternative['conversions']} conversions from #{worst_alternative['participants']} participants "
79
+ words += "(#{pretty_conversion_rate(worst_alternative)}). "
80
+
81
+ if (p.nil?)
82
+ words += "However, this difference is not statistically significant."
83
+ else
84
+ words += "This difference is #{PERCENTAGES[p]} likely to be statistically significant, which means you can be "
85
+ words += "#{DESCRIPTION_IN_WORDS[p]} that it is the result of your alternatives actually mattering, rather than "
86
+ words += "being due to random chance. However, this statistical test can't measure how likely the currently "
87
+ words += "observed magnitude of the difference is to be accurate or not. It only says \"better\", not \"better "
88
+ words += "by so much\"."
89
+ end
90
+ words
91
+ end
92
+
93
+ end
@@ -0,0 +1,43 @@
1
+ <h3><%= experiment['name'].titleize %> <%= %Q|(Test completed)| if experiment['final'] %> </h3>
2
+ <table class="experiment" style="border: 1px black;">
3
+ <tr class="header_row">
4
+ <th>Name</th>
5
+ <th>Participants</th>
6
+ <th>Conversions</th>
7
+ <th>Notes</th>
8
+ </tr>
9
+ <tr class="experiment_row">
10
+ <td>Experiment Total: </td>
11
+ <td><%= experiment['participants'] %> </td>
12
+ <td><%= experiment['conversions'] %> (<%= Abongo::Statistics.pretty_conversion_rate(experiment) %>)</td>
13
+ <td></td>
14
+ </tr>
15
+ <% alternatives = Abongo.get_alternatives(experiment['_id']).to_a %>
16
+ <% alternatives.each do |alternative| %>
17
+ <tr class="alternative_row">
18
+ <td>
19
+ <%= h alternative['content'] %>
20
+ </td>
21
+ <td><%= alternative['participants'] %></td>
22
+ <td><%= alternative['conversions'] %> (<%= Abongo::Statistics.pretty_conversion_rate(alternative) %>)</td>
23
+ <td>
24
+ <% unless experiment['final'] %>
25
+ <%= link_to("End experiment, picking this.", url_for(:id => alternative['_id'],
26
+ :action => "end_experiment"),
27
+ :method => :post,
28
+ :confirm => "Are you sure you want to terminate this experiment? This is not reversible."
29
+ ) %>
30
+ <% else %>
31
+ <% if alternative['content'] == experiment['final'] %>
32
+ <b>(All users seeing this.)</b>
33
+ <% end %>
34
+ <% end %>
35
+ </td>
36
+ </tr>
37
+ <% end %>
38
+ <tr>
39
+ <td colspan="4">
40
+ <b>Significance test results: </b><%= Abongo::Statistics.describe_result_in_words(experiment, alternatives) %>
41
+ </td>
42
+ </tr>
43
+ </table>
@@ -0,0 +1,20 @@
1
+ <div id="abongo_dashboard">
2
+ <p><h1>Welcome to your A/Bongo dashboard!</h1>
3
+
4
+ See <a href="http://www.bongocardcreator.com/abongo">the official site</a> for documentation.
5
+ I encourage you to customize this page to fit your needs. See /vendor/plugins/abongo/views for
6
+ the view templates. Hack them to pieces -- please!
7
+ </p>
8
+
9
+ <p>
10
+ <% if flash[:notice] %>
11
+ <span style="color: green;"><%= flash[:notice] %> </span>
12
+ <% end %>
13
+ <h2>All Experiments</h2>
14
+
15
+ <% @experiments.each do |experiment| %>
16
+ <%= render :partial => "dashboard/experiment", :locals => {:experiment => experiment} %>
17
+ <br/>
18
+ <% end %>
19
+ </p>
20
+ </div>
@@ -0,0 +1,51 @@
1
+ #This module exists entirely to save finger strain for programmers.
2
+ #It is designed to be included in your ApplicationController.
3
+ #
4
+ #See abongo.rb for descriptions of what these do.
5
+
6
+ module AbongoSugar
7
+
8
+ def ab_test(test_name, alternatives = nil, options = {})
9
+ if (Abongo.options[:enable_specification] && !params[test_name].nil?)
10
+ choice = params[test_name]
11
+ elsif (Abongo.options[:enable_override_in_session] && !session[test_name].nil?)
12
+ choice = session[test_name]
13
+ elsif (Abongo.options[:enable_selection] && !params[test_name].nil?)
14
+ choice = alternatives[params[test_name].to_i]
15
+ elsif (alternatives.nil?)
16
+ choice = Abongo.flip(test_name, options)
17
+ else
18
+ choice = Abongo.test(test_name, alternatives, options)
19
+ end
20
+
21
+ if block_given?
22
+ yield(choice)
23
+ else
24
+ choice
25
+ end
26
+ end
27
+
28
+ def bongo!(test_name, options = {})
29
+ Abongo.bongo!(test_name, options)
30
+ end
31
+
32
+ #Mark the user as a human.
33
+ def abongo_mark_human
34
+ textual_result = "1"
35
+ begin
36
+ a = params[:a].to_i
37
+ b = params[:b].to_i
38
+ c = params[:c].to_i
39
+ if (request.method == :post && (a + b == c))
40
+ Abongo.human!
41
+ else
42
+ textual_result = "0"
43
+ end
44
+ rescue #If a bot doesn't pass a, b, or c, to_i will fail. This scarfs up the exception, to save it from polluting our logs.
45
+ textual_result = "0"
46
+ end
47
+ render :text => textual_result, :layout => false #Not actually used by browser
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,43 @@
1
+ #Gives you easy syntax to use ABongo in your views.
2
+
3
+ module AbongoViewHelper
4
+
5
+ def ab_test(test_name, alternatives = nil, options = {}, &block)
6
+
7
+ if (Abongo.options[:enable_specification] && !params[test_name].nil?)
8
+ choice = params[test_name]
9
+ elsif (Abongo.options[:enable_override_in_session] && !session[test_name].nil?)
10
+ choice = session[test_name]
11
+ elsif (Abongo.options[:enable_selection] && !params[test_name].nil?)
12
+ choice = alternatives[params[test_name].to_i]
13
+ elsif (alternatives.nil?)
14
+ choice = Abongo.flip(test_name, options)
15
+ else
16
+ choice = Abongo.test(test_name, alternatives, options)
17
+ end
18
+
19
+ if block
20
+ content_tag = capture(choice, &block)
21
+ Rails::VERSION::MAJOR <= 2 and block_called_from_erb?(block) ? concat(content_tag) : content_tag
22
+ else
23
+ choice
24
+ end
25
+ end
26
+
27
+ def bongo!(test_name, options = {})
28
+ Abongo.bongo!(test_name, options)
29
+ end
30
+
31
+ #This causes an AJAX post against the URL. That URL should call Abongo.human!
32
+ #This guarantees that anyone calling Abongo.human! is capable of at least minimal Javascript execution, and thus is (probably) not a robot.
33
+ def include_humanizing_javascript(url = "/abongo_mark_human", style = :prototype)
34
+ script = nil
35
+ if (style == :prototype)
36
+ script = "var a=Math.floor(Math.random()*11); var b=Math.floor(Math.random()*11);var x=new Ajax.Request('#{url}', {parameters:{a: a, b: b, c: a+b}})"
37
+ elsif (style == :jquery)
38
+ script = "var a=Math.floor(Math.random()*11); var b=Math.floor(Math.random()*11);var x=jQuery.post('#{url}', {a: a, b: b, c: a+b})"
39
+ end
40
+ script.nil? ? "" : %Q|<script type="text/javascript">#{script}</script>|.html_safe
41
+ end
42
+
43
+ end
@@ -0,0 +1,2 @@
1
+ ActionController::Base.send :include, AbongoSugar
2
+ ActionView::Base.send :include, AbongoViewHelper
@@ -0,0 +1,2 @@
1
+ ActionController::Base.send :include, AbongoSugar
2
+ ActionView::Base.send :include, AbongoViewHelper
@@ -0,0 +1,514 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'mongo'
4
+ require 'abongo'
5
+
6
+ class TestAbongo < Test::Unit::TestCase
7
+
8
+ def setup
9
+ conn = Mongo::Connection.new
10
+ db = conn['abongo_test']
11
+ Abongo.db = db
12
+ Abongo.options = {}
13
+ Abongo.identity = nil
14
+ Abongo.salt = 'Not really necessary.'
15
+ Abongo.participants.drop
16
+ Abongo.alternatives.drop
17
+ Abongo.conversions.drop
18
+ Abongo.experiments.drop
19
+ end
20
+
21
+ def teardown
22
+ end
23
+
24
+ def test_experiment_creation
25
+ experiment = Abongo.start_experiment!('test_test', ['alt1', 'alt2'])
26
+ assert_equal('test_test', experiment['name'])
27
+ assert_equal(['alt1', 'alt2'], experiment['alternatives'])
28
+ assert(Abongo.tests_listening_to_conversion('test_test').include?(experiment['_id']))
29
+ end
30
+
31
+ def test_experiment_creation_occurs_once
32
+ experiment1 = Abongo.start_experiment!('test_test', ['alt1', 'alt2'])
33
+ experiment2 = Abongo.start_experiment!('test_test', ['alt1', 'alt2'])
34
+ assert_equal(experiment1, experiment2)
35
+ end
36
+
37
+ def test_experiment_creation_with_conversion
38
+ Abongo.start_experiment!('test_test', ['alt1', 'alt2'], 'convert')
39
+ experiment = Abongo.get_test('test_test')
40
+ assert_equal('test_test', experiment['name'])
41
+ assert_equal(['alt1', 'alt2'], experiment['alternatives'])
42
+ assert(Abongo.tests_listening_to_conversion('convert').include?(experiment['_id']))
43
+ end
44
+
45
+ def test_default_identity
46
+ assert 0 <= Abongo.identity.to_i
47
+ assert 10**10 >= Abongo.identity.to_i
48
+ end
49
+
50
+ def test_add_participation
51
+ Abongo.add_participation('ident', 'test1')
52
+ assert_equal(['test1'], Abongo.find_participant('ident')['tests'])
53
+ Abongo.add_participation('ident', 'test2')
54
+ assert_equal(['test1', 'test2'], Abongo.find_participant('ident')['tests'])
55
+ end
56
+
57
+ def test_count_participation
58
+ Abongo.test('test_test', ['alt1', 'alt2'])
59
+ experiment = Abongo.get_test 'test_test'
60
+ assert_equal 1, experiment['participants']
61
+ end
62
+
63
+ def test_add_conversions
64
+ Abongo.add_conversion('ident', 'test1')
65
+ assert_equal(['test1'], Abongo.find_participant('ident')['conversions'])
66
+ Abongo.add_conversion('ident', 'test2')
67
+ assert_equal(['test1', 'test2'], Abongo.find_participant('ident')['conversions'])
68
+ end
69
+
70
+ def test_find_alternative_for_user
71
+ Abongo.identity = 'ident'
72
+ experiment = Abongo.start_experiment!('test_test', ['alt1', 'alt2'])
73
+ assert_equal('alt1', Abongo.find_alternative_for_user('ident', experiment))
74
+ end
75
+
76
+ def test_test
77
+ Abongo.identity = 'ident'
78
+ assert_equal('alt1', Abongo.test('test_test', ['alt1', 'alt2']))
79
+ experiment = Abongo.get_test('test_test')
80
+ assert_equal('test_test', experiment['name'])
81
+ assert_equal(['alt1', 'alt2'], experiment['alternatives'])
82
+ assert_equal([experiment['_id']], Abongo.find_participant('ident')['tests'])
83
+ end
84
+
85
+ def test_test_with_block
86
+ Abongo.identity = 'ident'
87
+ Abongo.test('test_test', ['alt1', 'alt2']){|alt|
88
+ assert_equal('alt1', alt)
89
+ }
90
+ end
91
+
92
+ def test_flip
93
+ Abongo.identity = 'ident'
94
+ assert_equal(true, Abongo.flip('test_test'))
95
+ experiment = Abongo.get_test('test_test')
96
+ assert_equal('test_test', experiment['name'])
97
+ assert_equal([true, false], experiment['alternatives'])
98
+ assert_equal([experiment['_id']], Abongo.find_participant('ident')['tests'])
99
+ end
100
+
101
+ def test_flip_with_block
102
+ Abongo.identity = 'ident'
103
+ Abongo.flip('test_test'){|alt|
104
+ assert_equal(true, alt)
105
+ }
106
+ experiment = Abongo.get_test('test_test')
107
+ assert_equal([true, false], experiment['alternatives'])
108
+ end
109
+
110
+ def test_flip_with_options
111
+ Abongo.identity = 'ident'
112
+ Abongo.flip('test_test', :conversion => 'test_conversions')
113
+ experiment = Abongo.get_test('test_test')
114
+ assert_equal([experiment['_id']], Abongo.tests_listening_to_conversion('test_conversions'))
115
+ end
116
+
117
+ def test_flip_with_options_and_block
118
+ Abongo.identity = 'ident'
119
+ Abongo.flip('test_test', :conversion => 'test_conversions') do |alt|
120
+ # do nothing
121
+ end
122
+ experiment = Abongo.get_test('test_test')
123
+ assert_equal([experiment['_id']], Abongo.tests_listening_to_conversion('test_conversions'))
124
+ end
125
+
126
+ def test_test_short_circuit
127
+ Abongo.identity = 'ident'
128
+ assert_equal('alt1', Abongo.test('test_test', ['alt1', 'alt2']))
129
+ Abongo.end_experiment!('test_test', 'alt2')
130
+ assert_equal('alt2', Abongo.test('test_test', ['alt1', 'alt2']))
131
+ Abongo.end_experiment!('test_test', 'alt3')
132
+ assert_equal('alt3', Abongo.test('test_test', ['alt1', 'alt2']))
133
+ end
134
+
135
+ def test_ensure_one_participation_per_participant
136
+ Abongo.identity = 'ident'
137
+ 10.times do
138
+ Abongo.test('test_test', ['alt1', 'alt2'])
139
+ participant = Abongo.find_participant('ident')
140
+ assert_equal(1, participant['tests'].size)
141
+ alternative = Abongo.alternatives.find_one(:test => participant['tests'].first, :content => 'alt1')
142
+ assert_equal(1, alternative['participants'])
143
+ end
144
+ end
145
+
146
+ def test_ensure_multiple_participation
147
+ Abongo.identity = 'ident'
148
+ 10.times do |num|
149
+ Abongo.test('test_test', ['alt1', 'alt2'], {:multiple_participation => true})
150
+ participant = Abongo.find_participant('ident')
151
+ assert_equal(1, participant['tests'].size)
152
+ alternative = Abongo.alternatives.find_one(:test => participant['tests'].first, :content => 'alt1')
153
+ assert_equal(num+1, alternative['participants'])
154
+ end
155
+ end
156
+
157
+ def test_score_conversion
158
+ Abongo.identity = 'ident'
159
+ Abongo.test('test_test', ['alt1', 'alt2'])
160
+ participant = Abongo.find_participant('ident')
161
+ alternative = Abongo.alternatives.find_one(:test => participant['tests'].first, :content => 'alt1')
162
+ assert_equal(1, alternative['participants'])
163
+ assert_equal(0, alternative['conversions'])
164
+ experiment = Abongo.get_test('test_test')
165
+ Abongo.score_conversion!(experiment['_id'])
166
+ alternative = Abongo.alternatives.find_one(:test => participant['tests'].first, :content => 'alt1')
167
+ assert_equal(1, alternative['conversions'])
168
+ end
169
+
170
+ def test_count_participation
171
+ Abongo.test('test_test', ['alt1', 'alt2'])
172
+ experiment = Abongo.get_test 'test_test'
173
+ assert_equal 0, experiment['conversions'] || 0
174
+ Abongo.score_conversion!(experiment['_id'])
175
+ experiment = Abongo.get_test 'test_test'
176
+ assert_equal 1, experiment['conversions'] || 0
177
+ end
178
+
179
+ def test_score_conversion_with_name
180
+ Abongo.identity = 'ident'
181
+ Abongo.test('test_test', ['alt1', 'alt2'])
182
+ participant = Abongo.find_participant('ident')
183
+ alternative = Abongo.alternatives.find_one(:test => participant['tests'].first, :content => 'alt1')
184
+ assert_equal(1, alternative['participants'])
185
+ assert_equal(0, alternative['conversions'])
186
+ experiment = Abongo.get_test('test_test')
187
+ Abongo.score_conversion!('test_test')
188
+ alternative = Abongo.alternatives.find_one(:test => participant['tests'].first, :content => 'alt1')
189
+ assert_equal(1, alternative['conversions'])
190
+ end
191
+
192
+ def test_score_conversion_only_once_per_participant
193
+ Abongo.identity = 'ident'
194
+ Abongo.test('test_test', ['alt1', 'alt2'])
195
+ participant = Abongo.find_participant('ident')
196
+ alternative = Abongo.alternatives.find_one(:test => participant['tests'].first, :content => 'alt1')
197
+ assert_equal(1, alternative['participants'])
198
+ assert_equal(0, alternative['conversions'])
199
+ experiment = Abongo.get_test('test_test')
200
+ 10.times do
201
+ Abongo.score_conversion!(experiment['_id'])
202
+ alternative = Abongo.alternatives.find_one(:test => participant['tests'].first, :content => 'alt1')
203
+ assert_equal(1, alternative['conversions'])
204
+ end
205
+ end
206
+
207
+ def test_score_conversion_with_multiple_conversions
208
+ Abongo.identity = 'ident'
209
+ Abongo.options[:multiple_conversions] = true
210
+ Abongo.test('test_test', ['alt1', 'alt2'])
211
+ experiment = Abongo.get_test('test_test')
212
+ alternative = Abongo.alternatives.find_one(:test => experiment["_id"], :content => 'alt1')
213
+ assert_equal(1, alternative['participants'])
214
+ assert_equal(0, alternative['conversions'])
215
+ 10.times do |num|
216
+ Abongo.score_conversion!(experiment['_id'])
217
+ alternative = Abongo.alternatives.find_one(:test => experiment["_id"], :content => 'alt1')
218
+ assert_equal(num+1, alternative['conversions'])
219
+ end
220
+ end
221
+
222
+ def test_salt
223
+ Abongo.identity = 'ident'
224
+ assert_equal('alt1', Abongo.test('test_test', ['alt1', 'alt2']))
225
+ Abongo.salt = "This will change the result"
226
+ assert_equal('alt2', Abongo.test('test_test', ['alt1', 'alt2']))
227
+ end
228
+
229
+ def test_count_humans_only
230
+ Abongo.identity = 'ident'
231
+ Abongo.options[:count_humans_only] = true
232
+ assert_equal("alt1", Abongo.test('test_test', ['alt1', 'alt2']))
233
+
234
+ experiment = Abongo.get_test('test_test')
235
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
236
+ assert_equal(0, experiment['participants'])
237
+ assert_equal(0, experiment['conversions'])
238
+ assert_equal(0, alternative['participants'])
239
+ assert_equal(0, alternative['conversions'])
240
+
241
+ Abongo.score_conversion!('test_test')
242
+ experiment = Abongo.get_test('test_test')
243
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
244
+ assert_equal(0, experiment['participants'])
245
+ assert_equal(0, experiment['conversions'])
246
+ assert_equal(0, alternative['participants'])
247
+ assert_equal(0, alternative['conversions'])
248
+
249
+ Abongo.human!
250
+ experiment = Abongo.get_test('test_test')
251
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
252
+ assert_equal(1, experiment['participants'])
253
+ assert_equal(1, experiment['conversions'])
254
+ assert_equal(1, alternative['participants'])
255
+ assert_equal(1, alternative['conversions'])
256
+
257
+
258
+ assert_equal("alt1", Abongo.test('test2', ['alt1', 'alt2']))
259
+
260
+ experiment = Abongo.get_test('test2')
261
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
262
+ assert_equal(1, experiment['participants'])
263
+ assert_equal(0, experiment['conversions'])
264
+ assert_equal(1, alternative['participants'])
265
+ assert_equal(0, alternative['conversions'])
266
+
267
+ Abongo.score_conversion!('test2')
268
+ experiment = Abongo.get_test('test2')
269
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
270
+ assert_equal(1, experiment['participants'])
271
+ assert_equal(1, experiment['conversions'])
272
+ assert_equal(1, alternative['participants'])
273
+ assert_equal(1, alternative['conversions'])
274
+
275
+ Abongo.human!
276
+ experiment = Abongo.get_test('test2')
277
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
278
+ assert_equal(1, experiment['participants'])
279
+ assert_equal(1, experiment['conversions'])
280
+ assert_equal(1, alternative['participants'])
281
+ assert_equal(1, alternative['conversions'])
282
+ end
283
+
284
+ def test_count_humans_only_without_conversion_before_marked_human
285
+ Abongo.identity = 'ident'
286
+ Abongo.options[:count_humans_only] = true
287
+ assert_equal("alt1", Abongo.test('test_test', ['alt1', 'alt2']))
288
+
289
+ experiment = Abongo.get_test('test_test')
290
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
291
+ assert_equal(0, alternative['participants'])
292
+ assert_equal(0, alternative['conversions'])
293
+
294
+ Abongo.human!
295
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
296
+ assert_equal(1, alternative['participants'])
297
+ assert_equal(0, alternative['conversions'])
298
+
299
+ Abongo.score_conversion!('test_test')
300
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
301
+ assert_equal(1, alternative['participants'])
302
+ assert_equal(1, alternative['conversions'])
303
+ end
304
+
305
+ def test_parse_alternatives_array
306
+ assert_equal([1, 5, 2, 4, true], Abongo.parse_alternatives([1, 5, 2, 4, true]))
307
+ end
308
+
309
+ def test_parse_alternatives_integer
310
+ assert_equal([1, 2, 3, 4, 5], Abongo.parse_alternatives(5))
311
+ end
312
+
313
+ def test_parse_alternatives_range
314
+ assert_equal([2, 3, 4, 5], Abongo.parse_alternatives(2..5))
315
+ end
316
+
317
+ def test_parse_alternatives_hash
318
+ assert_equal(["three", "three", "three", "one"], Abongo.parse_alternatives({"three" => 3, "one" => 1}))
319
+ end
320
+
321
+ def test_parse_alternatives_hash_invalid_value
322
+ assert_raise RuntimeError do
323
+ Abongo.parse_alternatives({"three" => "bob"})
324
+ end
325
+ end
326
+
327
+ def test_parse_alternatives_invalid_type
328
+ assert_raise RuntimeError do
329
+ Abongo.parse_alternatives(Abongo.new)
330
+ end
331
+ end
332
+
333
+ def test_bongo_array
334
+ Abongo.identity = 'ident'
335
+ test1 = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
336
+ test2 = Abongo.start_experiment!('test2', ['alt1', 'alt2'])
337
+ test3 = Abongo.start_experiment!('test3', ['alt1', 'alt2'])
338
+ test4 = Abongo.start_experiment!('test3', ['alt1', 'alt2'])
339
+ Abongo.test('test1', ['alt1', 'alt2'])
340
+ Abongo.test('test2', ['alt1', 'alt2'])
341
+ Abongo.test('test3', ['alt1', 'alt2'])
342
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
343
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test2), :test => test2['_id']})['conversions'])
344
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test3), :test => test3['_id']})['conversions'])
345
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test4), :test => test4['_id']})['conversions'])
346
+
347
+ Abongo.bongo!(['test1', 'test2'])
348
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
349
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test2), :test => test2['_id']})['conversions'])
350
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test3), :test => test3['_id']})['conversions'])
351
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test4), :test => test4['_id']})['conversions'])
352
+ end
353
+
354
+ def test_bongo_nil
355
+ Abongo.identity = 'ident'
356
+ test1 = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
357
+ test2 = Abongo.start_experiment!('test2', ['alt1', 'alt2'])
358
+ test3 = Abongo.start_experiment!('test3', ['alt1', 'alt2'])
359
+ Abongo.test('test1', ['alt1', 'alt2'])
360
+ Abongo.test('test2', ['alt1', 'alt2'])
361
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
362
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test2), :test => test2['_id']})['conversions'])
363
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test3), :test => test3['_id']})['conversions'])
364
+
365
+ Abongo.bongo!
366
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
367
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test2), :test => test2['_id']})['conversions'])
368
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test3), :test => test3['_id']})['conversions'])
369
+ end
370
+
371
+ def test_bongo_with_alternative_conversion
372
+ Abongo.identity = 'ident'
373
+ test1 = Abongo.start_experiment!('test1', ['alt1', 'alt2'], "convert!")
374
+ test2 = Abongo.start_experiment!('test2', ['alt1', 'alt2'], "convert!")
375
+ test3 = Abongo.start_experiment!('test3', ['alt1', 'alt2'], "dontconvert!")
376
+ Abongo.test('test1', ['alt1', 'alt2'])
377
+ Abongo.test('test2', ['alt1', 'alt2'])
378
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
379
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test2), :test => test2['_id']})['conversions'])
380
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test3), :test => test3['_id']})['conversions'])
381
+
382
+ Abongo.bongo!('convert!')
383
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
384
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test2), :test => test2['_id']})['conversions'])
385
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test3), :test => test3['_id']})['conversions'])
386
+ end
387
+
388
+ def test_bongo_with_test_name
389
+ Abongo.identity = 'ident'
390
+ test1 = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
391
+ test2 = Abongo.start_experiment!('test2', ['alt1', 'alt2'])
392
+ test3 = Abongo.start_experiment!('test3', ['alt1', 'alt2'])
393
+ Abongo.test('test1', ['alt1', 'alt2'])
394
+ Abongo.test('test2', ['alt1', 'alt2'])
395
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
396
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test2), :test => test2['_id']})['conversions'])
397
+ Abongo.bongo!('test1')
398
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
399
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test2), :test => test2['_id']})['conversions'])
400
+ end
401
+
402
+ def test_bongo_doesnt_assume_participation
403
+ Abongo.identity = 'ident'
404
+ test1 = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
405
+ Abongo.bongo!('test1')
406
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
407
+ Abongo.test('test1', ['alt1', 'alt2'])
408
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
409
+ Abongo.bongo!('test1')
410
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
411
+ end
412
+
413
+ def test_bongo_with_assume_participation
414
+ Abongo.identity = 'ident'
415
+ Abongo.options[:assume_participation] = true
416
+ test1 = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
417
+ assert_equal(0, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
418
+ Abongo.bongo!('test1')
419
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
420
+ Abongo.test('test1', ['alt1', 'alt2'])
421
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
422
+ Abongo.bongo!('test1')
423
+ assert_equal(1, Abongo.alternatives.find_one({:content => Abongo.find_alternative_for_user(Abongo.identity, test1), :test => test1['_id']})['conversions'])
424
+ end
425
+
426
+ def test_participating_tests
427
+ Abongo.identity = 'ident'
428
+ test1 = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
429
+ test2 = Abongo.start_experiment!('test2', ['alt1', 'alt2'])
430
+ test3 = Abongo.start_experiment!('test3', ['alt1', 'alt2'])
431
+ assert_equal({}, Abongo.participating_tests)
432
+ Abongo.test('test1', ['alt1', 'alt2'])
433
+ assert_equal({'test1' => 'alt1'}, Abongo.participating_tests)
434
+ Abongo.test('test2', ['alt1', 'alt2'])
435
+ assert_equal({'test1' => 'alt1', 'test2' => 'alt1'}, Abongo.participating_tests)
436
+ Abongo.end_experiment!('test1', 'alt1')
437
+ assert_equal({'test2' => 'alt1'}, Abongo.participating_tests)
438
+ Abongo.end_experiment!('test2', 'alt1')
439
+ assert_equal({}, Abongo.participating_tests)
440
+ end
441
+
442
+ def test_participating_tests_with_noncurrent
443
+ Abongo.identity = 'ident'
444
+ test1 = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
445
+ test2 = Abongo.start_experiment!('test2', ['alt1', 'alt2'])
446
+ test3 = Abongo.start_experiment!('test3', ['alt1', 'alt2'])
447
+ assert_equal({}, Abongo.participating_tests)
448
+ Abongo.test('test1', ['alt1', 'alt2'])
449
+ assert_equal({'test1' => 'alt1'}, Abongo.participating_tests(false))
450
+ Abongo.test('test2', ['alt1', 'alt2'])
451
+ assert_equal({'test1' => 'alt1', 'test2' => 'alt1'}, Abongo.participating_tests(false))
452
+ Abongo.end_experiment!('test1', 'alt1')
453
+ assert_equal({'test1' => 'alt1', 'test2' => 'alt1'}, Abongo.participating_tests(false))
454
+ Abongo.end_experiment!('test2', 'alt1')
455
+ assert_equal({'test1' => 'alt1', 'test2' => 'alt1'}, Abongo.participating_tests(false))
456
+ end
457
+
458
+ def test_expires_in
459
+ Abongo.identity = 'ident'
460
+ Abongo.options[:expires_in] = 1
461
+ experiment = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
462
+
463
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
464
+ assert_equal(0, alternative['participants'])
465
+ assert_equal(0, alternative['conversions'])
466
+
467
+ Abongo.test('test1', ['alt1', 'alt2'])
468
+ Abongo.bongo!
469
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
470
+ assert_equal(1, alternative['participants'])
471
+ assert_equal(1, alternative['conversions'])
472
+
473
+ sleep(1)
474
+
475
+ Abongo.test('test1', ['alt1', 'alt2'])
476
+ Abongo.bongo!
477
+ alternative = Abongo.alternatives.find_one(:test => experiment['_id'], :content => 'alt1')
478
+ assert_equal(2, alternative['participants'])
479
+ assert_equal(2, alternative['conversions'])
480
+ end
481
+
482
+ def test_expires_in_for_bots
483
+ Abongo.identity = 'ident'
484
+ Abongo.options[:count_humans_only] = true
485
+ Abongo.options[:expires_in] = 1000
486
+ Abongo.options[:expires_in_for_bots] = 1
487
+ experiment = Abongo.start_experiment!('test1', ['alt1', 'alt2'])
488
+
489
+ Abongo.test('test1', ['alt1', 'alt2'])
490
+ participant = Abongo.find_participant(Abongo.identity)
491
+ assert(participant['expires'] < Time.now+1)
492
+
493
+ Abongo.human!
494
+ participant = Abongo.find_participant(Abongo.identity)
495
+ assert(participant['expires'] > Time.now+1)
496
+
497
+ Abongo.identity = 'ident2'
498
+ Abongo.human!
499
+ Abongo.test('test1', ['alt1', 'alt2'])
500
+ participant = Abongo.find_participant(Abongo.identity)
501
+ assert(participant['expires'] > Time.now+1)
502
+ end
503
+
504
+ def test_get_test_accepts_name
505
+ experiment = Abongo.start_experiment!('test_test', ['alt1', 'alt2'])
506
+ assert_equal(experiment, Abongo.get_test('test_test'))
507
+ end
508
+
509
+ def test_get_test_accepts_id
510
+ experiment = Abongo.start_experiment!('test_test', ['alt1', 'alt2'])
511
+ assert_equal(experiment, Abongo.get_test(experiment['_id']))
512
+ end
513
+
514
+ end