abongo 0.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.
@@ -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