reportable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,574 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Saulabs::Reportable::Report do
4
+
5
+ before do
6
+ @report = Saulabs::Reportable::Report.new(User, :registrations)
7
+ @now = Time.now
8
+ DateTime.stub!(:now).and_return(@now)
9
+ end
10
+
11
+ describe '#options' do
12
+
13
+ it 'should be frozen' do
14
+ @report.options.should be_frozen
15
+ end
16
+
17
+ end
18
+
19
+ describe '#run' do
20
+
21
+ it 'should process the data with the report cache' do
22
+ Saulabs::Reportable::ReportCache.should_receive(:process).once.with(
23
+ @report,
24
+ { :limit => 100, :grouping => @report.options[:grouping], :conditions => [], :live_data => false, :end_date => false }
25
+ )
26
+
27
+ @report.run
28
+ end
29
+
30
+ it 'should process the data with the report cache when custom conditions are given' do
31
+ Saulabs::Reportable::ReportCache.should_receive(:process).once.with(
32
+ @report,
33
+ { :limit => 100, :grouping => @report.options[:grouping], :conditions => { :some => :condition }, :live_data => false, :end_date => false }
34
+ )
35
+
36
+ @report.run(:conditions => { :some => :condition })
37
+ end
38
+
39
+ it 'should validate the specified options for the :run context' do
40
+ @report.should_receive(:ensure_valid_options).once.with({ :limit => 3 }, :run)
41
+
42
+ result = @report.run(:limit => 3)
43
+ end
44
+
45
+ it 'should use a custom grouping if one is specified' do
46
+ grouping = Saulabs::Reportable::Grouping.new(:month)
47
+ Saulabs::Reportable::Grouping.should_receive(:new).once.with(:month).and_return(grouping)
48
+ Saulabs::Reportable::ReportCache.should_receive(:process).once.with(
49
+ @report,
50
+ { :limit => 100, :grouping => grouping, :conditions => [], :live_data => false, :end_date => false }
51
+ )
52
+
53
+ @report.run(:grouping => :month)
54
+ end
55
+
56
+ it 'should return an array of the same length as the specified limit when :live_data is false' do
57
+ @report = Saulabs::Reportable::Report.new(User, :cumulated_registrations, :limit => 10, :live_data => false)
58
+
59
+ @report.run.length.should == 10
60
+ end
61
+
62
+ it 'should return an array of the same length as the specified limit + 1 when :live_data is true' do
63
+ @report = Saulabs::Reportable::Report.new(User, :cumulated_registrations, :limit => 10, :live_data => true)
64
+
65
+ @report.run.length.should == 11
66
+ end
67
+
68
+ for grouping in [:hour, :day, :week, :month] do
69
+
70
+ describe "for grouping :#{grouping.to_s}" do
71
+
72
+ before(:all) do
73
+ User.create!(:login => 'test 1', :created_at => Time.now, :profile_visits => 2)
74
+ User.create!(:login => 'test 2', :created_at => Time.now - 1.send(grouping), :profile_visits => 1)
75
+ User.create!(:login => 'test 3', :created_at => Time.now - 3.send(grouping), :profile_visits => 2)
76
+ User.create!(:login => 'test 4', :created_at => Time.now - 3.send(grouping), :profile_visits => 3)
77
+ end
78
+
79
+ describe 'when :end_date is specified' do
80
+
81
+ it 'should not raise a SQL duplicate key error after multiple runs' do
82
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
83
+ :limit => 2,
84
+ :grouping => grouping,
85
+ :end_date => Date.yesterday.to_datetime
86
+ )
87
+ @report.run
88
+ lambda { @report.run }.should_not raise_error
89
+ end
90
+
91
+ describe 'the returned result' do
92
+
93
+ before do
94
+ @end_date = DateTime.now - 1.send(grouping)
95
+ @grouping = Saulabs::Reportable::Grouping.new(grouping)
96
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
97
+ :grouping => grouping,
98
+ :limit => 10,
99
+ :end_date => @end_date
100
+ )
101
+ @result = @report.run
102
+ end
103
+
104
+ it "should start with the reporting period (end_date - limit.#{grouping.to_s})" do
105
+ @result.first[0].should == Saulabs::Reportable::ReportingPeriod.new(@grouping, @end_date - 9.send(grouping)).date_time
106
+ end
107
+
108
+ it "should end with the reporting period of the specified end date" do
109
+ @result.last[0].should == Saulabs::Reportable::ReportingPeriod.new(@grouping, @end_date).date_time
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+
116
+ [true, false].each do |live_data|
117
+
118
+ describe "with :live_data = #{live_data}" do
119
+
120
+ describe 'the returned result' do
121
+
122
+ before do
123
+ Saulabs::Reportable::ReportCache.delete_all
124
+ @grouping = Saulabs::Reportable::Grouping.new(grouping)
125
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
126
+ :grouping => grouping,
127
+ :limit => 10,
128
+ :live_data => live_data
129
+ )
130
+ @result = @report.run
131
+ end
132
+
133
+ it "should be an array starting reporting period (Time.now - limit.#{grouping.to_s})" do
134
+ @result.first[0].should == Saulabs::Reportable::ReportingPeriod.new(@grouping, Time.now - 10.send(grouping)).date_time
135
+ end
136
+
137
+ if live_data
138
+ it "should be data ending with the current reporting period" do
139
+ @result.last[0].should == Saulabs::Reportable::ReportingPeriod.new(@grouping).date_time
140
+ end
141
+ else
142
+ it "should be data ending with the reporting period before the current" do
143
+ @result.last[0].should == Saulabs::Reportable::ReportingPeriod.new(@grouping).previous.date_time
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ it 'should return correct data for aggregation :count' do
150
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
151
+ :aggregation => :count,
152
+ :grouping => grouping,
153
+ :limit => 10,
154
+ :live_data => live_data
155
+ )
156
+ result = @report.run.to_a
157
+
158
+ result[10][1].should == 1.0 if live_data
159
+ result[9][1].should == 1.0
160
+ result[8][1].should == 0.0
161
+ result[7][1].should == 2.0
162
+ result[6][1].should == 0.0
163
+ end
164
+
165
+ it 'should return correct data for aggregation :sum' do
166
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
167
+ :aggregation => :sum,
168
+ :grouping => grouping,
169
+ :value_column => :profile_visits,
170
+ :limit => 10,
171
+ :live_data => live_data
172
+ )
173
+ result = @report.run().to_a
174
+
175
+ result[10][1].should == 2.0 if live_data
176
+ result[9][1].should == 1.0
177
+ result[8][1].should == 0.0
178
+ result[7][1].should == 5.0
179
+ result[6][1].should == 0.0
180
+ end
181
+
182
+ it 'should return correct data for aggregation :maximum' do
183
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
184
+ :aggregation => :maximum,
185
+ :grouping => grouping,
186
+ :value_column => :profile_visits,
187
+ :limit => 10,
188
+ :live_data => live_data
189
+ )
190
+ result = @report.run().to_a
191
+
192
+ result[10][1].should == 2.0 if live_data
193
+ result[9][1].should == 1.0
194
+ result[8][1].should == 0.0
195
+ result[7][1].should == 3.0
196
+ result[6][1].should == 0.0
197
+ end
198
+
199
+ it 'should return correct data for aggregation :minimum' do
200
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
201
+ :aggregation => :minimum,
202
+ :grouping => grouping,
203
+ :value_column => :profile_visits,
204
+ :limit => 10,
205
+ :live_data => live_data
206
+ )
207
+ result = @report.run().to_a
208
+
209
+ result[10][1].should == 2.0 if live_data
210
+ result[9][1].should == 1.0
211
+ result[8][1].should == 0.0
212
+ result[7][1].should == 2.0
213
+ result[6][1].should == 0.0
214
+ end
215
+
216
+ it 'should return correct data for aggregation :average' do
217
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
218
+ :aggregation => :average,
219
+ :grouping => grouping,
220
+ :value_column => :profile_visits,
221
+ :limit => 10,
222
+ :live_data => live_data
223
+ )
224
+ result = @report.run().to_a
225
+
226
+ result[10][1].should == 2.0 if live_data
227
+ result[9][1].should == 1.0
228
+ result[8][1].should == 0.0
229
+ result[7][1].should == 2.5
230
+ result[6][1].should == 0.0
231
+ end
232
+
233
+ it 'should return correct data for aggregation :count when custom conditions are specified' do
234
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
235
+ :aggregation => :count,
236
+ :grouping => grouping,
237
+ :limit => 10,
238
+ :live_data => live_data
239
+ )
240
+ result = @report.run(:conditions => ['login IN (?)', ['test 1', 'test 2', 'test 4']]).to_a
241
+
242
+ result[10][1].should == 1.0 if live_data
243
+ result[9][1].should == 1.0
244
+ result[8][1].should == 0.0
245
+ result[7][1].should == 1.0
246
+ result[6][1].should == 0.0
247
+ end
248
+
249
+ it 'should return correct data for aggregation :sum when custom conditions are specified' do
250
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
251
+ :aggregation => :sum,
252
+ :grouping => grouping,
253
+ :value_column => :profile_visits,
254
+ :limit => 10,
255
+ :live_data => live_data
256
+ )
257
+ result = @report.run(:conditions => ['login IN (?)', ['test 1', 'test 2', 'test 4']]).to_a
258
+
259
+ result[10][1].should == 2.0 if live_data
260
+ result[9][1].should == 1.0
261
+ result[8][1].should == 0.0
262
+ result[7][1].should == 3.0
263
+ result[6][1].should == 0.0
264
+ end
265
+
266
+ it 'should return correct results when run twice in a row with a higher limit on the second run' do
267
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
268
+ :aggregation => :count,
269
+ :grouping => grouping,
270
+ :limit => 10,
271
+ :live_data => live_data
272
+ )
273
+ result = @report.run(:limit => 2).to_a
274
+
275
+ result[2][1].should == 1.0 if live_data
276
+ result[1][1].should == 1.0
277
+ result[0][1].should == 0.0
278
+
279
+ result = @report.run(:limit => 10).to_a
280
+
281
+ result[10][1].should == 1.0 if live_data
282
+ result[9][1].should == 1.0
283
+ result[8][1].should == 0.0
284
+ result[7][1].should == 2.0
285
+ result[6][1].should == 0.0
286
+ end
287
+
288
+ unless live_data
289
+
290
+ it 'should return correct data for aggregation :count when :end_date is specified' do
291
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
292
+ :aggregation => :count,
293
+ :grouping => grouping,
294
+ :limit => 10,
295
+ :end_date => Time.now - 3.send(grouping)
296
+ )
297
+ result = @report.run.to_a
298
+
299
+ result[9][1].should == 2.0
300
+ result[8][1].should == 0.0
301
+ result[7][1].should == 0.0
302
+ result[6][1].should == 0.0
303
+ end
304
+
305
+ it 'should return correct data for aggregation :sum when :end_date is specified' do
306
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
307
+ :aggregation => :sum,
308
+ :grouping => grouping,
309
+ :value_column => :profile_visits,
310
+ :limit => 10,
311
+ :end_date => Time.now - 3.send(grouping)
312
+ )
313
+ result = @report.run.to_a
314
+
315
+ result[9][1].should == 5.0
316
+ result[8][1].should == 0.0
317
+ result[7][1].should == 0.0
318
+ result[6][1].should == 0.0
319
+ end
320
+
321
+ it 'should return correct results when run twice in a row with an end date further in the past on the second run' do
322
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
323
+ :aggregation => :count,
324
+ :grouping => grouping,
325
+ :limit => 10,
326
+ :live_data => live_data
327
+ )
328
+ result = @report.run(:end_date => Time.now - 1.send(grouping)).to_a
329
+
330
+ result[9][1].should == 1.0
331
+ result[8][1].should == 0.0
332
+ result[7][1].should == 2.0
333
+
334
+ result = @report.run(:end_date => Time.now - 3.send(grouping)).to_a
335
+
336
+ result[9][1].should == 2.0
337
+ result[8][1].should == 0.0
338
+ result[7][1].should == 0.0
339
+ end
340
+
341
+ end
342
+
343
+ end
344
+
345
+ end
346
+
347
+ after(:all) do
348
+ User.destroy_all
349
+ end
350
+
351
+ end
352
+
353
+ end
354
+
355
+ describe 'for grouping week with data ranging over two years' do
356
+
357
+ describe 'with the first week of the second year belonging to the first year' do
358
+
359
+ before(:all) do
360
+ User.create!(:login => 'test 1', :created_at => DateTime.new(2008, 12, 22))
361
+ User.create!(:login => 'test 2', :created_at => DateTime.new(2008, 12, 29))
362
+ User.create!(:login => 'test 3', :created_at => DateTime.new(2009, 1, 4))
363
+ User.create!(:login => 'test 4', :created_at => DateTime.new(2009, 1, 5))
364
+ User.create!(:login => 'test 5', :created_at => DateTime.new(2009, 1, 12))
365
+
366
+ Time.stub!(:now).and_return(DateTime.new(2009, 1, 25))
367
+ end
368
+
369
+ it 'should return correct data for aggregation :count' do
370
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
371
+ :aggregation => :count,
372
+ :grouping => :week,
373
+ :limit => 10
374
+ )
375
+ result = @report.run.to_a
376
+
377
+ result[9][1].should == 0.0
378
+ result[8][1].should == 1.0
379
+ result[7][1].should == 1.0
380
+ result[6][1].should == 2.0
381
+ result[5][1].should == 1.0
382
+ end
383
+
384
+ end
385
+
386
+ describe 'with the first week of the second year belonging to the second year' do
387
+
388
+ before(:all) do
389
+ User.create!(:login => 'test 1', :created_at => DateTime.new(2009, 12, 21))
390
+ User.create!(:login => 'test 2', :created_at => DateTime.new(2009, 12, 28))
391
+ User.create!(:login => 'test 3', :created_at => DateTime.new(2010, 1, 3))
392
+ User.create!(:login => 'test 4', :created_at => DateTime.new(2010, 1, 4))
393
+ User.create!(:login => 'test 5', :created_at => DateTime.new(2010, 1, 11))
394
+
395
+ Time.stub!(:now).and_return(DateTime.new(2010, 1, 25))
396
+ end
397
+
398
+ it 'should return correct data for aggregation :count' do
399
+ @report = Saulabs::Reportable::Report.new(User, :registrations,
400
+ :aggregation => :count,
401
+ :grouping => :week,
402
+ :limit => 10
403
+ )
404
+ result = @report.run.to_a
405
+
406
+ result[9][1].should == 0.0
407
+ result[8][1].should == 1.0
408
+ result[7][1].should == 1.0
409
+ result[6][1].should == 2.0
410
+ result[5][1].should == 1.0
411
+ end
412
+
413
+ end
414
+
415
+ end
416
+
417
+ after do
418
+ Saulabs::Reportable::ReportCache.destroy_all
419
+ end
420
+
421
+ after(:all) do
422
+ User.destroy_all
423
+ end
424
+
425
+ end
426
+
427
+ describe '#read_data' do
428
+
429
+ it 'should invoke the aggregation method on the model' do
430
+ @report = Saulabs::Reportable::Report.new(User, :registrations, :aggregation => :count)
431
+ User.should_receive(:count).once.and_return([])
432
+
433
+ @report.send(:read_data, Time.now, 5.days.from_now, { :grouping => @report.options[:grouping], :conditions => [] })
434
+ end
435
+
436
+ it 'should setup the conditions' do
437
+ @report.should_receive(:setup_conditions).once.and_return([])
438
+
439
+ @report.send(:read_data, Time.now, 5.days.from_now, { :grouping => @report.options[:grouping], :conditions => [] })
440
+ end
441
+
442
+ end
443
+
444
+ describe '#setup_conditions' do
445
+
446
+ before do
447
+ @begin_at = Time.now
448
+ @end_at = 5.days.from_now
449
+ @created_at_column_clause = "#{ActiveRecord::Base.connection.quote_table_name('users')}.#{ActiveRecord::Base.connection.quote_column_name('created_at')}"
450
+ end
451
+
452
+ it 'should return conditions for date_column BETWEEN begin_at and end_at only when no custom conditions are specified and both begin and end date are specified' do
453
+ @report.send(:setup_conditions, @begin_at, @end_at).should == ["#{@created_at_column_clause} BETWEEN ? AND ?", @begin_at, @end_at]
454
+ end
455
+
456
+ it 'should return conditions for date_column >= begin_at when no custom conditions and a begin_at are specified' do
457
+ @report.send(:setup_conditions, @begin_at, nil).should == ["#{@created_at_column_clause} >= ?", @begin_at]
458
+ end
459
+
460
+ it 'should return conditions for date_column <= end_at when no custom conditions and a end_at are specified' do
461
+ @report.send(:setup_conditions, nil, @end_at).should == ["#{@created_at_column_clause} <= ?", @end_at]
462
+ end
463
+
464
+ it 'should raise an argument error when neither begin_at or end_at are specified' do
465
+ lambda {@report.send(:setup_conditions, nil, nil)}.should raise_error(ArgumentError)
466
+ end
467
+
468
+ it 'should return conditions for date_column BETWEEN begin_at and end_date only when an empty Hash of custom conditions is specified' do
469
+ @report.send(:setup_conditions, @begin_at, @end_at, {}).should == ["#{@created_at_column_clause} BETWEEN ? AND ?", @begin_at, @end_at]
470
+ end
471
+
472
+ it 'should return conditions for date_column BETWEEN begin_at and end_date only when an empty Array of custom conditions is specified' do
473
+ @report.send(:setup_conditions, @begin_at, @end_at, []).should == ["#{@created_at_column_clause} BETWEEN ? AND ?", @begin_at, @end_at]
474
+ end
475
+
476
+ it 'should correctly include custom conditions if they are specified as a Hash' do
477
+ custom_conditions = { :first_name => 'first name', :last_name => 'last name' }
478
+
479
+ conditions = @report.send(:setup_conditions, @begin_at, @end_at, custom_conditions)
480
+ # cannot directly check for string equqlity here since hashes are not ordered and so there is no way to now in which order the conditions are added to the SQL clause
481
+ conditions[0].should =~ (/#{ActiveRecord::Base.connection.quote_table_name('users')}.#{ActiveRecord::Base.connection.quote_column_name('first_name')} = #{ActiveRecord::Base.connection.quote('first name')}/)
482
+ conditions[0].should =~ (/#{ActiveRecord::Base.connection.quote_table_name('users')}.#{ActiveRecord::Base.connection.quote_column_name('last_name')} = #{ActiveRecord::Base.connection.quote('last name')}/)
483
+ conditions[0].should =~ (/#{@created_at_column_clause} BETWEEN \? AND \?/)
484
+ conditions[1].should == @begin_at
485
+ conditions[2].should == @end_at
486
+ end
487
+
488
+ it 'should correctly include custom conditions if they are specified as an Array' do
489
+ custom_conditions = ['first_name = ? AND last_name = ?', 'first name', 'last name']
490
+
491
+ @report.send(:setup_conditions, @begin_at, @end_at, custom_conditions).should == ["first_name = #{ActiveRecord::Base.connection.quote('first name')} AND last_name = #{ActiveRecord::Base.connection.quote('last name')} AND #{@created_at_column_clause} BETWEEN ? AND ?", @begin_at, @end_at]
492
+ end
493
+
494
+ end
495
+
496
+ describe '#ensure_valid_options' do
497
+
498
+ it 'should raise an error if malformed conditions are specified' do
499
+ lambda { @report.send(:ensure_valid_options, { :conditions => 1 }) }.should raise_error(ArgumentError)
500
+ end
501
+
502
+ it 'should not raise an error if conditions are specified as an Array' do
503
+ lambda { @report.send(:ensure_valid_options, { :conditions => ['first_name = ?', 'first name'] }) }.should_not raise_error(ArgumentError)
504
+ end
505
+
506
+ it 'should not raise an error if conditions are specified as a Hash' do
507
+ lambda { @report.send(:ensure_valid_options, { :conditions => { :first_name => 'first name' } }) }.should_not raise_error(ArgumentError)
508
+ end
509
+
510
+ it 'should raise an error if an invalid grouping is specified' do
511
+ lambda { @report.send(:ensure_valid_options, { :grouping => :decade }) }.should raise_error(ArgumentError)
512
+ end
513
+
514
+ it 'should raise an error if an end date is specified that is not a DateTime' do
515
+ lambda { @report.send(:ensure_valid_options, { :end_date => 'today' }) }.should raise_error(ArgumentError)
516
+ end
517
+
518
+ it 'should raise an error if an end date is specified that is in the future' do
519
+ lambda { @report.send(:ensure_valid_options, { :end_date => (DateTime.now + 1.month) }) }.should raise_error(ArgumentError)
520
+ end
521
+
522
+ it 'should raise an error if both an end date and :live_data = true are specified' do
523
+ lambda { @report.send(:ensure_valid_options, { :end_date => DateTime.now, :live_data => true }) }.should raise_error(ArgumentError)
524
+ end
525
+
526
+ it 'should not raise an error if both an end date and :live_data = false are specified' do
527
+ lambda { @report.send(:ensure_valid_options, { :end_date => DateTime.now, :live_data => false }) }.should_not raise_error
528
+ end
529
+
530
+ describe 'for context :initialize' do
531
+
532
+ it 'should not raise an error if valid options are specified' do
533
+ lambda { @report.send(:ensure_valid_options, {
534
+ :limit => 100,
535
+ :aggregation => :count,
536
+ :grouping => :day,
537
+ :date_column => :created_at,
538
+ :value_column => :id,
539
+ :conditions => [],
540
+ :live_data => true
541
+ })
542
+ }.should_not raise_error(ArgumentError)
543
+ end
544
+
545
+ it 'should raise an error if an unsupported option is specified' do
546
+ lambda { @report.send(:ensure_valid_options, { :invalid => :option }) }.should raise_error(ArgumentError)
547
+ end
548
+
549
+ it 'should raise an error if an invalid aggregation is specified' do
550
+ lambda { @report.send(:ensure_valid_options, { :aggregation => :invalid }) }.should raise_error(ArgumentError)
551
+ end
552
+
553
+ it 'should raise an error if aggregation :sum is spesicied but no :value_column' do
554
+ lambda { @report.send(:ensure_valid_options, { :aggregation => :sum }) }.should raise_error(ArgumentError)
555
+ end
556
+
557
+ end
558
+
559
+ describe 'for context :run' do
560
+
561
+ it 'should not raise an error if valid options are specified' do
562
+ lambda { @report.send(:ensure_valid_options, { :limit => 100, :conditions => [], :grouping => :week, :live_data => true }, :run)
563
+ }.should_not raise_error(ArgumentError)
564
+ end
565
+
566
+ it 'should raise an error if an unsupported option is specified' do
567
+ lambda { @report.send(:ensure_valid_options, { :aggregation => :sum }, :run) }.should raise_error(ArgumentError)
568
+ end
569
+
570
+ end
571
+
572
+ end
573
+
574
+ end