neuron-client 0.1.0 → 0.2.0
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.
- data/README.md +34 -8
- data/lib/neuron-client.rb +61 -12
- data/lib/neuron-client/{connection.rb → admin_connection.rb} +7 -7
- data/lib/neuron-client/api.rb +48 -31
- data/lib/neuron-client/membase_connection.rb +18 -0
- data/lib/neuron-client/model/admin/ad.rb +22 -0
- data/lib/neuron-client/model/admin/ad_zone.rb +15 -0
- data/lib/neuron-client/model/admin/base.rb +91 -0
- data/lib/neuron-client/model/admin/blocked_referer.rb +12 -0
- data/lib/neuron-client/model/admin/blocked_user_agent.rb +12 -0
- data/lib/neuron-client/model/admin/geo_target.rb +16 -0
- data/lib/neuron-client/model/admin/report.rb +15 -0
- data/lib/neuron-client/model/admin/s3_file.rb +12 -0
- data/lib/neuron-client/model/admin/zone.rb +15 -0
- data/lib/neuron-client/model/base.rb +38 -0
- data/lib/neuron-client/model/common/ad.rb +40 -0
- data/lib/neuron-client/model/common/ad_calculations.rb +329 -0
- data/lib/neuron-client/model/common/ad_zone.rb +17 -0
- data/lib/neuron-client/model/common/base.rb +67 -0
- data/lib/neuron-client/model/common/blocked_referer.rb +16 -0
- data/lib/neuron-client/model/common/blocked_user_agent.rb +16 -0
- data/lib/neuron-client/model/common/geo_target.rb +16 -0
- data/lib/neuron-client/model/common/report.rb +21 -0
- data/lib/neuron-client/model/common/s3_file.rb +16 -0
- data/lib/neuron-client/model/common/zone.rb +22 -0
- data/lib/neuron-client/model/common/zone_calculations.rb +41 -0
- data/lib/neuron-client/model/membase/ad.rb +31 -0
- data/lib/neuron-client/model/membase/ad_zone.rb +11 -0
- data/lib/neuron-client/model/membase/blocked_referer.rb +18 -0
- data/lib/neuron-client/model/membase/blocked_user_agent.rb +18 -0
- data/lib/neuron-client/model/membase/geo_target.rb +11 -0
- data/lib/neuron-client/model/membase/report.rb +11 -0
- data/lib/neuron-client/model/membase/s3_file.rb +11 -0
- data/lib/neuron-client/model/membase/zone.rb +19 -0
- data/lib/neuron-client/model/models.rb +14 -0
- data/lib/neuron-client/version.rb +1 -1
- data/neuron-client.gemspec +18 -11
- data/spec/fixtures/vcr_cassettes/s3_file.yml +186 -4
- data/spec/lib/admin_connection_spec.rb +82 -0
- data/spec/lib/api_spec.rb +80 -0
- data/spec/lib/membase_connection_spec.rb +27 -0
- data/spec/lib/model/admin/ad_spec.rb +34 -0
- data/spec/lib/model/admin/ad_zone_spec.rb +19 -0
- data/spec/lib/model/admin/base_spec.rb +11 -0
- data/spec/lib/model/admin/blocked_referer_spec.rb +11 -0
- data/spec/lib/model/admin/blocked_user_agent_spec.rb +11 -0
- data/spec/lib/model/admin/geo_target_spec.rb +30 -0
- data/spec/lib/model/admin/report_spec.rb +21 -0
- data/spec/lib/model/admin/s3_spec.rb +11 -0
- data/spec/lib/model/admin/zone_spec.rb +21 -0
- data/spec/lib/model/base_spec.rb +89 -0
- data/spec/lib/model/common/ad_calculations_spec.rb +1148 -0
- data/spec/lib/model/common/ad_spec.rb +11 -0
- data/spec/lib/model/common/ad_zone_spec.rb +11 -0
- data/spec/lib/model/common/base_spec.rb +11 -0
- data/spec/lib/model/common/blocked_referer_spec.rb +11 -0
- data/spec/lib/model/common/blocked_user_agent_spec.rb +11 -0
- data/spec/lib/model/common/geo_target_spec.rb +11 -0
- data/spec/lib/model/common/report_spec.rb +11 -0
- data/spec/lib/model/common/s3_spec.rb +11 -0
- data/spec/lib/model/common/zone_calculations_spec.rb +54 -0
- data/spec/lib/model/common/zone_spec.rb +11 -0
- data/spec/lib/model/membase/ad_spec.rb +50 -0
- data/spec/lib/model/membase/ad_zone_spec.rb +11 -0
- data/spec/lib/model/membase/base_spec.rb +11 -0
- data/spec/lib/model/membase/blocked_referer_spec.rb +30 -0
- data/spec/lib/model/membase/blocked_user_agent_spec.rb +30 -0
- data/spec/lib/model/membase/geo_target_spec.rb +11 -0
- data/spec/lib/model/membase/report_spec.rb +11 -0
- data/spec/lib/model/membase/s3_spec.rb +11 -0
- data/spec/lib/model/membase/zone_spec.rb +28 -0
- data/spec/lib/old_spec.rb +192 -149
- data/spec/lib/s3_file_spec.rb +45 -42
- data/spec/spec_helper.rb +2 -1
- metadata +296 -57
- data/lib/neuron-client/ad.rb +0 -39
- data/lib/neuron-client/ad_zone.rb +0 -16
- data/lib/neuron-client/blocked_referer.rb +0 -12
- data/lib/neuron-client/blocked_user_agent.rb +0 -12
- data/lib/neuron-client/connected.rb +0 -138
- data/lib/neuron-client/geo_target.rb +0 -16
- data/lib/neuron-client/real_time_stats.rb +0 -0
- data/lib/neuron-client/report.rb +0 -20
- data/lib/neuron-client/s3_file.rb +0 -10
- data/lib/neuron-client/zone.rb +0 -16
@@ -0,0 +1,30 @@
|
|
1
|
+
module Neuron
|
2
|
+
module Client
|
3
|
+
module Model
|
4
|
+
module Admin
|
5
|
+
describe GeoTarget do
|
6
|
+
describe "self.query(parameters)" do
|
7
|
+
it "should call the expected methods and return the expected value" do
|
8
|
+
c = stub(:connection)
|
9
|
+
GeoTarget.should_receive(:connection).and_return(c)
|
10
|
+
p = stub(:parameters)
|
11
|
+
gta = stub(:geo_target_attributes)
|
12
|
+
gta2 = stub(:geo_target_attributes)
|
13
|
+
r = [
|
14
|
+
{'geo_target' => gta},
|
15
|
+
{'geo_target' => gta2}
|
16
|
+
]
|
17
|
+
c.should_receive(:get).with('geo_targets', p).and_return(r)
|
18
|
+
g = stub(:geo_target)
|
19
|
+
g2 = stub(:geo_target2)
|
20
|
+
GeoTarget.should_receive(:new).with(gta).and_return(g)
|
21
|
+
GeoTarget.should_receive(:new).with(gta2).and_return(g2)
|
22
|
+
|
23
|
+
GeoTarget.query(p).should == [g, g2]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Neuron
|
2
|
+
module Client
|
3
|
+
module Model
|
4
|
+
module Admin
|
5
|
+
describe Report do
|
6
|
+
describe "result" do
|
7
|
+
it "should call the expected methods and return the expected result" do
|
8
|
+
r = Report.allocate
|
9
|
+
c = stub(:connection)
|
10
|
+
Report.should_receive(:connection).and_return(c)
|
11
|
+
r.should_receive(:id).and_return(7)
|
12
|
+
c.should_receive(:get).with('reports/7/result', :format => '').and_return('result_value')
|
13
|
+
|
14
|
+
r.result.should == 'result_value'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Neuron
|
2
|
+
module Client
|
3
|
+
module Model
|
4
|
+
module Admin
|
5
|
+
describe Zone do
|
6
|
+
describe "unlink(ad_id)" do
|
7
|
+
it "should call the expected method and return the expected results" do
|
8
|
+
z = Zone.allocate
|
9
|
+
c = stub(:connection)
|
10
|
+
Zone.should_receive(:connection).and_return(c)
|
11
|
+
z.should_receive(:id).and_return(1)
|
12
|
+
c.should_receive(:delete).with('zones/1/ads/2').and_return('result_value')
|
13
|
+
|
14
|
+
z.unlink(2).should == 'result_value'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Neuron
|
2
|
+
module Client
|
3
|
+
module Model
|
4
|
+
module Mock
|
5
|
+
class BaseMockModel; end
|
6
|
+
end
|
7
|
+
|
8
|
+
class BaseMockModel < Base
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Base do
|
13
|
+
describe "instance methods" do
|
14
|
+
describe "initialize(attrs=nil)" do
|
15
|
+
it "should set @proxied_model appropriately" do
|
16
|
+
pending "Not sure what the deal is here, it works outside of unit testing"
|
17
|
+
|
18
|
+
ctp = stub(:class_to_proxy)
|
19
|
+
Base.any_instance.should_receive(:class_to_proxy).and_return(ctp)
|
20
|
+
p = stub(:proxy)
|
21
|
+
ctp.should_receive(:new).with('attributes').and_return(p)
|
22
|
+
b = Base.new('attributes')
|
23
|
+
|
24
|
+
b.instance_variable_get(:@proxied_model).should == p
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "method_missing(meth, *args, &block)" do
|
29
|
+
context "when method exists on the proxied model" do
|
30
|
+
it "should call the method on the proxied model"
|
31
|
+
end
|
32
|
+
context "when method does not exist on the proxied model" do
|
33
|
+
it "should call super.method_missing"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "class methods" do
|
39
|
+
describe "api" do
|
40
|
+
context "when @api exists" do
|
41
|
+
it "should return the expected value" do
|
42
|
+
a = stub(:api)
|
43
|
+
Base.instance_variable_set(:@api, a)
|
44
|
+
|
45
|
+
Base.api.should == a
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context "when @api does not exist" do
|
49
|
+
it "should return the expected value" do
|
50
|
+
Base.instance_variable_set(:@api, nil)
|
51
|
+
a = stub(:default_api)
|
52
|
+
Neuron::Client::API.should_receive(:default_api).and_return(a)
|
53
|
+
|
54
|
+
Base.api.should == a
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "connection" do
|
60
|
+
it "should call the expected methods and return the expected result" do
|
61
|
+
a = stub(:api)
|
62
|
+
Base.should_receive(:api).and_return(a)
|
63
|
+
c = stub(:connection)
|
64
|
+
a.should_receive(:connection).and_return(c)
|
65
|
+
|
66
|
+
Base.connection.should == c
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "class_to_proxy" do
|
71
|
+
it "should call the expected methods and return the expected result" do
|
72
|
+
BaseMockModel.stub_chain(:api, :connection_type).and_return(:mock)
|
73
|
+
BaseMockModel.class_to_proxy.should == Neuron::Client::Model::Mock::BaseMockModel
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "method_missing(meth, *args, &block)" do
|
78
|
+
context "when method exists on the class to proxy" do
|
79
|
+
it "should call the method on the proxied model"
|
80
|
+
end
|
81
|
+
context "when method does not exist on the class to proxy" do
|
82
|
+
it "should call super.method_missing"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,1148 @@
|
|
1
|
+
module Neuron
|
2
|
+
module Client
|
3
|
+
module Model
|
4
|
+
module Common
|
5
|
+
|
6
|
+
class FakeAd
|
7
|
+
include AdCalculations
|
8
|
+
attr_accessor :start_datetime, :end_datetime, :time_zone, :day_partitions,
|
9
|
+
:daily_cap, :overall_cap, :ideal_impressions_per_hour, :total_impressed,
|
10
|
+
:today_impressed
|
11
|
+
def initialize(opts={})
|
12
|
+
opts.each { |sym, value| self.send("#{sym}=", value) }
|
13
|
+
self.time_zone ||= "Beijing"
|
14
|
+
self.total_impressed ||= 0
|
15
|
+
self.today_impressed ||= 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe AdCalculations do
|
20
|
+
context "given an Ad with date range Sat, 01 Jan 2011, 00:00 -> Tue, 01 Feb 2011, 12:00; Beijing time; Day parted for 9:00 - 17:00, M-F; Daily cap of 10; Overall cap of 100; total_impressed of 50; today_impressed of 5" do
|
21
|
+
before(:each) do
|
22
|
+
@ad = FakeAd.new(
|
23
|
+
:start_datetime => Time.new(2011,1,1,0,0,0,"+08:00"),
|
24
|
+
:end_datetime => Time.new(2011,2,1,12,0,0,"+08:00"),
|
25
|
+
:time_zone => "Beijing",
|
26
|
+
:day_partitions => "F"*24 + ("F"*9 + "T"*8 + "F"*7)*5 + "F"*24,
|
27
|
+
:daily_cap => 10,
|
28
|
+
:overall_cap => 100,
|
29
|
+
:ideal_impressions_per_hour => nil,
|
30
|
+
:total_impressed => 50,
|
31
|
+
:today_impressed => 5
|
32
|
+
)
|
33
|
+
end
|
34
|
+
it "should be inactive on December 10th, 2010" do
|
35
|
+
Timecop.freeze(Time.new(2010,12,10)) do
|
36
|
+
@ad.active?.should be_false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
it "should be inactive on January 1st, 2011" do
|
40
|
+
Timecop.freeze(Time.new(2011,1,1,10,0,0,"+08:00")) do
|
41
|
+
@ad.active?.should be_false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
it "should be inactive on January 3rd, 2011 at 5:00 AM" do
|
45
|
+
Timecop.freeze(Time.new(2011,1,3,5,0,0,"+08:00")) do
|
46
|
+
@ad.active?.should be_false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
context "on January 3rd, 2011 at 10:00 AM" do
|
50
|
+
before(:each) { Timecop.freeze(Time.new(2011,1,3,10,0,0,"+08:00")) }
|
51
|
+
after(:each) { Timecop.return }
|
52
|
+
it "should be active" do
|
53
|
+
@ad.active?.should be_true
|
54
|
+
end
|
55
|
+
it "should have a pressure of 10/7" do
|
56
|
+
@ad.pressure.should == 10 / 7.0
|
57
|
+
end
|
58
|
+
end
|
59
|
+
context "on January 3rd, 2011 at 16:00" do
|
60
|
+
before(:each) { Timecop.freeze(Time.new(2011,1,3,16,0,0,"+08:00")) }
|
61
|
+
after(:each) { Timecop.return }
|
62
|
+
it "should be active" do
|
63
|
+
@ad.active?.should be_true
|
64
|
+
end
|
65
|
+
it "should have a pressure of 10/1" do
|
66
|
+
@ad.pressure.should == 10
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context "on January 27th, 2011 at 16:00" do
|
70
|
+
before(:each) { Timecop.freeze(Time.new(2011,1,27,16,0,0,"+08:00")) }
|
71
|
+
after(:each) { Timecop.return }
|
72
|
+
it "should be active" do
|
73
|
+
@ad.active?.should be_true
|
74
|
+
end
|
75
|
+
it "should have a pressure of 50/20" do
|
76
|
+
@ad.pressure.should == 50 / 20.0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
it "should be inactive on February 1st, 2011 at 13:00" do
|
80
|
+
Timecop.freeze(Time.new(2011,2,1,13,0,0,"+08:00")) do
|
81
|
+
@ad.active?.should be_false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe ".active?" do
|
87
|
+
it "should call calculate_active?" do
|
88
|
+
@ad = FakeAd.new
|
89
|
+
@ad.should_receive(:calculate_active?).and_return(:result)
|
90
|
+
|
91
|
+
@ad.active?.should == :result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe ".pressure" do
|
96
|
+
it "should call calculate_pressure" do
|
97
|
+
@ad = FakeAd.new
|
98
|
+
@ad.should_receive(:calculate_pressure).and_return(:result)
|
99
|
+
|
100
|
+
@ad.pressure.should == :result
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe ".calculate_active?(time, total_impressed, today_impressed)" do
|
105
|
+
it "should have specs"
|
106
|
+
end
|
107
|
+
|
108
|
+
describe ".calculate_pressure(time, total_impressed, today_impressed, active=nil)" do
|
109
|
+
it "should have some specs related to when 'active' is passed in"
|
110
|
+
context "when daily_cap is present" do
|
111
|
+
context "and daily_cap precludes overall_cap from being met" do
|
112
|
+
it "should calculate the overall pressure" do
|
113
|
+
ad = FakeAd.new(:daily_cap => 10)
|
114
|
+
ad.stub(:daily_cap_precludes_overall_cap?).and_return(true)
|
115
|
+
ad.should_receive(:calculate_overall_pressure)
|
116
|
+
ad.should_not_receive(:calculate_today_pressure)
|
117
|
+
|
118
|
+
ad.calculate_pressure(Time.now, 50, 5)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
context "and daily_cap does not preclude the overall_cap from being met" do
|
122
|
+
it "should calculate the pressure for just today" do
|
123
|
+
ad = FakeAd.new(:daily_cap => 10)
|
124
|
+
ad.stub(:daily_cap_precludes_overall_cap?).and_return(false)
|
125
|
+
ad.should_not_receive(:calculate_overall_pressure)
|
126
|
+
ad.should_receive(:calculate_today_pressure)
|
127
|
+
|
128
|
+
ad.calculate_pressure(Time.now, 50, 5)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
context "when daily_cap is not present" do
|
133
|
+
context "when overall_cap is blank or end_datetime is blank" do
|
134
|
+
it "should use the ideal_impressions_per_hour" do
|
135
|
+
ad = FakeAd.new(:overall_cap => 100, :ideal_impressions_per_hour => 99.9)
|
136
|
+
ad.should_not_receive(:calculate_overall_pressure)
|
137
|
+
ad.should_not_receive(:calculate_today_pressure)
|
138
|
+
|
139
|
+
ad.calculate_pressure(Time.now, 50, 5).should == 99.9
|
140
|
+
end
|
141
|
+
end
|
142
|
+
context "when overall_cap is not blank and end_datetime is not blank" do
|
143
|
+
it "should calculate the overall pressure" do
|
144
|
+
ad = FakeAd.new(:overall_cap => 100, :end_datetime => Time.now + 1.month)
|
145
|
+
ad.should_receive(:calculate_overall_pressure)
|
146
|
+
ad.should_not_receive(:calculate_today_pressure)
|
147
|
+
|
148
|
+
ad.calculate_pressure(Time.now, 50, 5)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
describe ".start_in_time_zone" do
|
156
|
+
it "should return start_datetime, time zone adjusted" do
|
157
|
+
result = FakeAd.new(
|
158
|
+
:start_datetime => Time.utc(2011, 2, 1, 1, 59),
|
159
|
+
:time_zone => 'Eastern Time (US & Canada)'
|
160
|
+
).send(:start_in_time_zone)
|
161
|
+
formatted_result = "#{result.time.strftime('%a, %d %b %Y %H:%M:%S')} #{result.zone} #{result.formatted_offset}"
|
162
|
+
|
163
|
+
formatted_result.should == "Mon, 31 Jan 2011 20:59:00 EST -05:00"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe ".end_in_time_zone" do
|
168
|
+
context "when end_datetime is present" do
|
169
|
+
it "should return end_datetime, time zone adjusted" do
|
170
|
+
result = FakeAd.new(
|
171
|
+
:end_datetime => Time.utc(2011, 2, 1, 1, 59),
|
172
|
+
:time_zone => 'Eastern Time (US & Canada)'
|
173
|
+
).send(:end_in_time_zone)
|
174
|
+
formatted_result = "#{result.time.strftime('%a, %d %b %Y %H:%M:%S')} #{result.zone} #{result.formatted_offset}"
|
175
|
+
|
176
|
+
formatted_result.should == "Mon, 31 Jan 2011 20:59:00 EST -05:00"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
context "when end_datetime is not present" do
|
180
|
+
it "should return nil" do
|
181
|
+
FakeAd.new(:end_datetime => nil).send(:end_in_time_zone).should be_nil
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe ".daily_capped?" do
|
187
|
+
it "should be false when daily_cap is nil" do
|
188
|
+
FakeAd.new(:daily_cap => nil).send(:daily_capped?).should be_false
|
189
|
+
end
|
190
|
+
it "should be true when daily_cap is a positive integer > zero" do
|
191
|
+
FakeAd.new(:daily_cap => 42).send(:daily_capped?).should be_true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe ".overall_capped?" do
|
196
|
+
it "should be false when overall_cap is nil" do
|
197
|
+
FakeAd.new(:overall_cap => nil).send(:overall_capped?).should be_false
|
198
|
+
end
|
199
|
+
it "should be true when overall_cap is a positive integer > zero" do
|
200
|
+
FakeAd.new(:overall_cap => 42).send(:overall_capped?).should be_true
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe ".partitioned?" do
|
205
|
+
it "should be false when day_partitions is nil" do
|
206
|
+
FakeAd.new(:day_partitions => nil).send(:partitioned?).should be_false
|
207
|
+
end
|
208
|
+
it "should be true when day_partitions is 168 character string of Ts and Fs." do
|
209
|
+
p = ("T"*100 + "F"*68).split('').shuffle.join
|
210
|
+
FakeAd.new(:day_partitions => p).send(:partitioned?).should be_true
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def within_date_range
|
215
|
+
FakeAd.new(:start_datetime => @start, :end_datetime => @end).send(:within_date_range?, @now)
|
216
|
+
end
|
217
|
+
|
218
|
+
describe ".within_date_range?(date)" do
|
219
|
+
before(:all) do
|
220
|
+
@now = Time.now
|
221
|
+
end
|
222
|
+
context "when ad has no start date" do
|
223
|
+
context "when ad has no end date" do
|
224
|
+
it "should return true" do
|
225
|
+
@start = nil
|
226
|
+
@end = nil
|
227
|
+
within_date_range.should be_true
|
228
|
+
end
|
229
|
+
end
|
230
|
+
context "when before ad's end date" do
|
231
|
+
it "should return true" do
|
232
|
+
@start = nil
|
233
|
+
@end = @now + 1.day
|
234
|
+
within_date_range.should be_true
|
235
|
+
end
|
236
|
+
end
|
237
|
+
context "when on or beyond ad's end date" do
|
238
|
+
it "should return false" do
|
239
|
+
@start = nil
|
240
|
+
@end = @now
|
241
|
+
within_date_range.should be_false
|
242
|
+
@end = @now - 1.day
|
243
|
+
within_date_range.should be_false
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
context "when before ad's start date" do
|
248
|
+
context "when ad has no end date" do
|
249
|
+
it "should return false" do
|
250
|
+
@start = @now + 1.day
|
251
|
+
@end = nil
|
252
|
+
within_date_range.should be_false
|
253
|
+
end
|
254
|
+
end
|
255
|
+
context "when before ad's end date" do
|
256
|
+
it "should return false" do
|
257
|
+
@start = @now + 1.day
|
258
|
+
@end = @start + 1.day
|
259
|
+
within_date_range.should be_false
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
context "when on or beyond ad's start date" do
|
264
|
+
context "when ad has no end date" do
|
265
|
+
it "should return true" do
|
266
|
+
@start = @now
|
267
|
+
@end = nil
|
268
|
+
within_date_range.should be_true
|
269
|
+
@start = @now - 1.day
|
270
|
+
within_date_range.should be_true
|
271
|
+
end
|
272
|
+
end
|
273
|
+
context "when before ad's end date" do
|
274
|
+
it "should return true" do
|
275
|
+
@start = @now
|
276
|
+
@end = @now + 1.day
|
277
|
+
within_date_range.should be_true
|
278
|
+
@start = @now - 1.day
|
279
|
+
within_date_range.should be_true
|
280
|
+
end
|
281
|
+
end
|
282
|
+
context "when on or beyond ad's end date" do
|
283
|
+
it "should return false" do
|
284
|
+
@start = @now
|
285
|
+
@end = @now
|
286
|
+
within_date_range.should be_false
|
287
|
+
@start = @now - 2.days
|
288
|
+
@end = @now
|
289
|
+
within_date_range.should be_false
|
290
|
+
@end = @now - 1.day
|
291
|
+
within_date_range.should be_false
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
describe ".partitioned_hour?(time)" do
|
298
|
+
context "when day partitions exist" do
|
299
|
+
before(:all) do
|
300
|
+
@ad = FakeAd.new(:day_partitions => [
|
301
|
+
"TTTTTTTTTTFTTTTTTTTTTTTT", # Sunday
|
302
|
+
"TTTTTTTTTTTTTTTFTTTTTTTT", # Monday
|
303
|
+
"FTTTTTTTTTTTTTTTTTTTTTTT", # Tuesday
|
304
|
+
"TTTTTFTTTTTTTTTTTTTTTTTT", # Wednesday
|
305
|
+
"TTTTTTTTTTTTTTTTTTTTFTTT", # Thursday
|
306
|
+
"TTTTTTTTTTTTTTTTTTTTTTTF", # Friday
|
307
|
+
"TTTTTTTTTTTTFTTTTTTTTTTT" # Saturday
|
308
|
+
].join)
|
309
|
+
end
|
310
|
+
context "when on Sunday" do
|
311
|
+
context "when on day partition" do
|
312
|
+
it "should return true" do
|
313
|
+
time = Chronic.parse('Sunday @ 9 am')
|
314
|
+
@ad.send(:partitioned_hour?, time).should be_true
|
315
|
+
end
|
316
|
+
end
|
317
|
+
context "when not on day partition" do
|
318
|
+
it "should return false" do
|
319
|
+
time = Chronic.parse('Sunday @ 10 am')
|
320
|
+
@ad.send(:partitioned_hour?, time).should be_false
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
context "when on Monday" do
|
325
|
+
context "when on day partition" do
|
326
|
+
it "should return true" do
|
327
|
+
time = Chronic.parse('Monday @ 2 pm')
|
328
|
+
@ad.send(:partitioned_hour?, time).should be_true
|
329
|
+
end
|
330
|
+
end
|
331
|
+
context "when not on day partition" do
|
332
|
+
it "should return false" do
|
333
|
+
time = Chronic.parse('Monday @ 3 pm')
|
334
|
+
@ad.send(:partitioned_hour?, time).should be_false
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
context "when on Friday" do
|
339
|
+
context "when on day partition" do
|
340
|
+
it "should return true" do
|
341
|
+
time = Chronic.parse('Friday @ 10 pm')
|
342
|
+
@ad.send(:partitioned_hour?, time).should be_true
|
343
|
+
end
|
344
|
+
end
|
345
|
+
context "when not on day partition" do
|
346
|
+
it "should return false" do
|
347
|
+
time = Chronic.parse('Friday @ 11 pm')
|
348
|
+
@ad.send(:partitioned_hour?, time).should be_false
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
context "when on Saturday" do
|
353
|
+
context "when on day partition" do
|
354
|
+
it "should return true" do
|
355
|
+
time = Chronic.parse('Saturday @ 1 pm')
|
356
|
+
@ad.send(:partitioned_hour?, time).should be_true
|
357
|
+
end
|
358
|
+
end
|
359
|
+
context "when not on day partition" do
|
360
|
+
it "should return false" do
|
361
|
+
time = Chronic.parse('Saturday @ 12 pm')
|
362
|
+
@ad.send(:partitioned_hour?, time).should be_false
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def partitioned_day?(time)
|
370
|
+
nine_to_five = "F"*9 + "T"*8 + "F"*7
|
371
|
+
no_hours = "F" * 24
|
372
|
+
ad = FakeAd.new(:day_partitions => [nine_to_five, nine_to_five, no_hours, nine_to_five, nine_to_five, nine_to_five, no_hours].join)
|
373
|
+
ad.send(:partitioned_day?, time)
|
374
|
+
end
|
375
|
+
describe ".partitioned_day?(time)" do
|
376
|
+
context "when ad is partitioned from 9-5, every day of the week except Tuesday and Saturday" do
|
377
|
+
it "should return true on Sunday @ 5 am" do
|
378
|
+
partitioned_day?( Chronic.parse('Sunday @ 5 am') ).should be_true
|
379
|
+
end
|
380
|
+
it "should return true on Sunday @ 10 am" do
|
381
|
+
partitioned_day?( Chronic.parse('Sunday @ 10 am') ).should be_true
|
382
|
+
end
|
383
|
+
it "should return true on Sunday @ 10 pm" do
|
384
|
+
partitioned_day?( Chronic.parse('Sunday @ 10 pm') ).should be_true
|
385
|
+
end
|
386
|
+
it "should return true on Monday @ 5 am" do
|
387
|
+
partitioned_day?( Chronic.parse('Monday @ 5 am') ).should be_true
|
388
|
+
end
|
389
|
+
it "should return false on Tuesday @ 5 am" do
|
390
|
+
partitioned_day?( Chronic.parse('Tuesday @ 5 am') ).should be_false
|
391
|
+
end
|
392
|
+
it "should return false on Tuesday @ 10 am" do
|
393
|
+
partitioned_day?( Chronic.parse('Tuesday @ 10 am') ).should be_false
|
394
|
+
end
|
395
|
+
it "should return true on Wednesday @ 5 am" do
|
396
|
+
partitioned_day?( Chronic.parse('Wednesday @ 5 am') ).should be_true
|
397
|
+
end
|
398
|
+
it "should return true on Thursday @ 5 am" do
|
399
|
+
partitioned_day?( Chronic.parse('Thursday @ 5 am') ).should be_true
|
400
|
+
end
|
401
|
+
it "should return true on Friday @ 5 am" do
|
402
|
+
partitioned_day?( Chronic.parse('Friday @ 5 am') ).should be_true
|
403
|
+
end
|
404
|
+
it "should return false on Saturday @ 5 am" do
|
405
|
+
partitioned_day?( Chronic.parse('Saturday @ 5 am') ).should be_false
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
describe ".cap_met?(total_impressed, today_impressed)" do
|
411
|
+
context "when not daily capped" do
|
412
|
+
context "and not overall capped" do
|
413
|
+
it "should return false" do
|
414
|
+
FakeAd.new(:daily_cap => nil, :overall_cap => nil).send(:cap_met?, 10, 10).should be_false
|
415
|
+
end
|
416
|
+
end
|
417
|
+
context "and overall cap is 10" do
|
418
|
+
before(:each) do
|
419
|
+
@ad = FakeAd.new(:daily_cap => nil, :overall_cap => 10)
|
420
|
+
end
|
421
|
+
context "and total_impressed = 9" do
|
422
|
+
it "should return false" do
|
423
|
+
@ad.send(:cap_met?, 9, 0).should be_false
|
424
|
+
end
|
425
|
+
end
|
426
|
+
context "and total_impressed = 10" do
|
427
|
+
it "should return true" do
|
428
|
+
@ad.send(:cap_met?, 10, 0).should be_true
|
429
|
+
end
|
430
|
+
end
|
431
|
+
context "and total_impressed = 11" do
|
432
|
+
it "should return true" do
|
433
|
+
@ad.send(:cap_met?, 11, 0).should be_true
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
context "when daily cap is 10" do
|
439
|
+
context "and not overall capped" do
|
440
|
+
before(:each) do
|
441
|
+
@ad = FakeAd.new(:daily_cap => 10, :overall_cap => nil)
|
442
|
+
end
|
443
|
+
context "and today_impressed = 9" do
|
444
|
+
it "should return false" do
|
445
|
+
@ad.send(:cap_met?, 0, 9).should be_false
|
446
|
+
end
|
447
|
+
end
|
448
|
+
context "and today_impressed = 10" do
|
449
|
+
it "should return true" do
|
450
|
+
@ad.send(:cap_met?, 0, 10).should be_true
|
451
|
+
end
|
452
|
+
end
|
453
|
+
context "and today_impressed = 11" do
|
454
|
+
it "should return true" do
|
455
|
+
@ad.send(:cap_met?, 0, 11).should be_true
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
context "and overall cap is 100" do
|
460
|
+
before(:each) do
|
461
|
+
@ad = FakeAd.new(:daily_cap => 10, :overall_cap => 100)
|
462
|
+
end
|
463
|
+
context "and total < 100, today < 10" do
|
464
|
+
it "should return false" do
|
465
|
+
@ad.send(:cap_met?, 99, 9).should be_false
|
466
|
+
end
|
467
|
+
end
|
468
|
+
context "and total < 100, today = 10" do
|
469
|
+
it "should return true" do
|
470
|
+
@ad.send(:cap_met?, 99, 10).should be_true
|
471
|
+
end
|
472
|
+
end
|
473
|
+
context "and total = 100, today < 10" do
|
474
|
+
it "should return true" do
|
475
|
+
@ad.send(:cap_met?, 100, 9).should be_true
|
476
|
+
end
|
477
|
+
end
|
478
|
+
context "and total = 100, today = 10" do
|
479
|
+
it "should return true" do
|
480
|
+
@ad.send(:cap_met?, 100, 10).should be_true
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def precluded
|
488
|
+
@time = Time.now
|
489
|
+
@total = 500
|
490
|
+
@today = 100
|
491
|
+
ad = FakeAd.new(:daily_cap => @daily_cap, :overall_cap => @overall_cap, :end_datetime => @end_datetime)
|
492
|
+
ad.stub(:remaining_impressions_via_daily_cap).with(@time, @today).and_return(@remaining_via_daily_cap)
|
493
|
+
ad.stub(:remaining_impressions_via_overall_cap).with(@total).and_return(@remaining_via_overall_cap)
|
494
|
+
ad.send(:daily_cap_precludes_overall_cap?, @time, @total, @today)
|
495
|
+
end
|
496
|
+
describe ".daily_cap_precludes_overall_cap?(time, total_impressed, today_impressed)" do
|
497
|
+
context "when ad has no daily cap" do
|
498
|
+
it "should return false" do
|
499
|
+
@daily_cap = nil
|
500
|
+
@overall_cap = 1
|
501
|
+
precluded.should be_false
|
502
|
+
end
|
503
|
+
end
|
504
|
+
context "when ad has no overall cap" do
|
505
|
+
it "should return false" do
|
506
|
+
@daily_cap = 1
|
507
|
+
@overall_cap = nil
|
508
|
+
precluded.should be_false
|
509
|
+
end
|
510
|
+
end
|
511
|
+
context "when ad has no end date/time" do
|
512
|
+
it "should return false" do
|
513
|
+
@daily_cap = 1
|
514
|
+
@overall_cap = 1
|
515
|
+
@end_datetime = nil
|
516
|
+
precluded.should be_false
|
517
|
+
end
|
518
|
+
end
|
519
|
+
context "when ad's daily cap is 10, overall cap is 100, and end datetime is present" do
|
520
|
+
before(:each) { @daily_cap = 10; @overall_cap = 100; @end_datetime = Time.now + 10.days }
|
521
|
+
context "when remaining_impressions_via_daily_cap < remaining_impressions_via_overall_cap" do
|
522
|
+
it "returns true" do
|
523
|
+
@remaining_via_daily_cap = 1
|
524
|
+
@remaining_via_overall_cap = 2
|
525
|
+
precluded.should be_true
|
526
|
+
end
|
527
|
+
end
|
528
|
+
context "when remaining_impressions_via_daily_cap = remaining_impressions_via_overall_cap" do
|
529
|
+
it "returns false" do
|
530
|
+
@remaining_via_daily_cap = 1
|
531
|
+
@remaining_via_overall_cap = 1
|
532
|
+
precluded.should be_false
|
533
|
+
end
|
534
|
+
end
|
535
|
+
context "when remaining_impressions_via_daily_cap > remaining_impressions_via_overall_cap" do
|
536
|
+
it "returns false" do
|
537
|
+
@remaining_via_daily_cap = 2
|
538
|
+
@remaining_via_overall_cap = 1
|
539
|
+
precluded.should be_false
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
describe ".remaining_impressions_via_overall_cap(total_impressed)" do
|
546
|
+
context "when overall_cap = 100" do
|
547
|
+
before(:each) { @ad = FakeAd.new(:overall_cap => 100) }
|
548
|
+
context "when total_impressed = 0" do
|
549
|
+
it "returns 100" do
|
550
|
+
@ad.send(:remaining_impressions_via_overall_cap, 0).should == 100
|
551
|
+
end
|
552
|
+
end
|
553
|
+
context "when total_impressed = 99" do
|
554
|
+
it "returns 1" do
|
555
|
+
@ad.send(:remaining_impressions_via_overall_cap, 99).should == 1
|
556
|
+
end
|
557
|
+
end
|
558
|
+
context "when total_impressed = 100" do
|
559
|
+
it "returns 0" do
|
560
|
+
@ad.send(:remaining_impressions_via_overall_cap, 100).should == 0
|
561
|
+
end
|
562
|
+
end
|
563
|
+
context "when total_impressed = 101" do
|
564
|
+
it "returns 0" do
|
565
|
+
@ad.send(:remaining_impressions_via_overall_cap, 101).should == 0
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
describe ".remaining_impressions_today(today_impressed)" do
|
572
|
+
context "when daily_cap = 100" do
|
573
|
+
before(:each) { @ad = FakeAd.new(:daily_cap => 100) }
|
574
|
+
context "when today_impressed = 0" do
|
575
|
+
it "returns 100" do
|
576
|
+
@ad.send(:remaining_impressions_today, 0).should == 100
|
577
|
+
end
|
578
|
+
end
|
579
|
+
context "when today_impressed = 99" do
|
580
|
+
it "returns 1" do
|
581
|
+
@ad.send(:remaining_impressions_today, 99).should == 1
|
582
|
+
end
|
583
|
+
end
|
584
|
+
context "when today_impressed = 100" do
|
585
|
+
it "returns 0" do
|
586
|
+
@ad.send(:remaining_impressions_today, 100).should == 0
|
587
|
+
end
|
588
|
+
end
|
589
|
+
context "when today_impressed = 101" do
|
590
|
+
it "returns 0" do
|
591
|
+
@ad.send(:remaining_impressions_today, 101).should == 0
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
describe ".remaining_impressions_via_daily_cap(time, today_impressed)" do
|
598
|
+
context "when daily_cap is 100, ad has 5 days remaining (including today), and 30 remaining impressions today" do
|
599
|
+
it "returns 430" do
|
600
|
+
ad = FakeAd.new(:daily_cap => 100)
|
601
|
+
ad.stub(:remaining_days).with(:time).and_return(5)
|
602
|
+
ad.stub(:remaining_impressions_today).with(:today_impressed).and_return(30)
|
603
|
+
|
604
|
+
ad.send(:remaining_impressions_via_daily_cap, :time, :today_impressed).should == 430
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
describe ".calculate_today_pressure(time, today_impressed)" do
|
610
|
+
context "when there are 3 hours and 12 impressions remaining today" do
|
611
|
+
it "should return a pressure of 8 (impressions/hr)" do
|
612
|
+
ad = FakeAd.new
|
613
|
+
ad.stub(:remaining_hours_today).with(:time).and_return(3)
|
614
|
+
ad.stub(:remaining_impressions_today).with(:today_impressed).and_return(12)
|
615
|
+
|
616
|
+
ad.send(:calculate_today_pressure, :time, :today_impressed).should == 8
|
617
|
+
end
|
618
|
+
end
|
619
|
+
context "when there are 0 hours and 12 impressions remaining today" do
|
620
|
+
it "should return a pressure of 0 (impressions/hr)" do
|
621
|
+
ad = FakeAd.new
|
622
|
+
ad.stub(:remaining_hours_today).with(:time).and_return(0)
|
623
|
+
ad.stub(:remaining_impressions_today).with(:today_impressed).and_return(12)
|
624
|
+
|
625
|
+
ad.send(:calculate_today_pressure, :time, :today_impressed).should == 0
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
describe ".calculate_overall_pressure(time, total_impressed)" do
|
631
|
+
context "when there are 3 hours and 12 impressions remaining overall" do
|
632
|
+
it "should return a pressure of 4 (impressions/hr)" do
|
633
|
+
ad = FakeAd.new
|
634
|
+
ad.stub(:remaining_hours).with(:time).and_return(3)
|
635
|
+
ad.stub(:remaining_impressions_via_overall_cap).with(:total_impressed).and_return(12)
|
636
|
+
|
637
|
+
ad.send(:calculate_overall_pressure, :time, :total_impressed).should == 4
|
638
|
+
end
|
639
|
+
end
|
640
|
+
context "when there are 0 hours and 12 impressions remaining overall" do
|
641
|
+
it "should return a pressure of 0 (impressions/hr)" do
|
642
|
+
ad = FakeAd.new
|
643
|
+
ad.stub(:remaining_hours).with(:time).and_return(0)
|
644
|
+
ad.stub(:remaining_impressions_via_overall_cap).with(:total_impressed).and_return(12)
|
645
|
+
|
646
|
+
ad.send(:calculate_overall_pressure, :time, :total_impressed).should == 0
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
def remaining_days(time)
|
652
|
+
FakeAd.new(
|
653
|
+
:start_datetime => @start,
|
654
|
+
:end_datetime => @end,
|
655
|
+
:day_partitions => @day_partitions).send(:remaining_days, time.in_time_zone("Beijing"))
|
656
|
+
end
|
657
|
+
describe ".remaining_days(time)" do
|
658
|
+
context "when time is on or after ad's end_datetime" do
|
659
|
+
it "should return zero" do
|
660
|
+
@start = nil
|
661
|
+
@end = Time.at(1234567890)
|
662
|
+
remaining_days(@end).should == 0
|
663
|
+
remaining_days(@end + 1.day).should == 0
|
664
|
+
end
|
665
|
+
end
|
666
|
+
context "when time is before ad's end_datetime" do
|
667
|
+
context "and ad is not day-partitioned" do
|
668
|
+
before(:each) { @day_partitions = nil }
|
669
|
+
context "and time is before ad's start_datetime" do
|
670
|
+
it "returns the number of days between the ad's start and end, including partial days on either end" do
|
671
|
+
@start = Time.parse("2011-11-11 11:11 +08:00")
|
672
|
+
@end = @start + 2.days
|
673
|
+
remaining_days(@start - 1.week).should == 3
|
674
|
+
end
|
675
|
+
end
|
676
|
+
context "and time is after the ad's start_datetime" do
|
677
|
+
it "should return the number of days between the given time and the ad's end" do
|
678
|
+
@start = Time.parse("2011-11-11 11:11 +08:00")
|
679
|
+
@time = @start + 1.day
|
680
|
+
@end = @time + 1.day
|
681
|
+
remaining_days(@time).should == 2
|
682
|
+
end
|
683
|
+
context "and the ad's end occurs at midnight" do
|
684
|
+
it "should return the number of days between the given time and the ad's end, not counting the morning of the final date" do
|
685
|
+
@start = Time.parse("2011-11-11 11:11 +08:00")
|
686
|
+
@time = @start + 1.day
|
687
|
+
@end = @time.end_of_day + 1.day
|
688
|
+
remaining_days(@time).should == 2
|
689
|
+
end
|
690
|
+
end
|
691
|
+
context "and the time is on the same day as the ad's end" do
|
692
|
+
it "should return one" do
|
693
|
+
@start = Time.parse("2011-11-11 11:11 +08:00")
|
694
|
+
@time = @start + 1.day
|
695
|
+
@end = @time + 1.hour
|
696
|
+
remaining_days(@time).should == 1
|
697
|
+
end
|
698
|
+
end
|
699
|
+
context "and the ad's end is at midnight the night after the given time" do
|
700
|
+
it "should return one" do
|
701
|
+
@start = Time.parse("2011-11-11 11:11 +08:00")
|
702
|
+
@time = @start + 1.day
|
703
|
+
@end = @time.end_of_day
|
704
|
+
remaining_days(@time).should == 1
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
end
|
709
|
+
context "when ad is partitioned with the first 12 hours of each weekday active, and the last 12 and the whole weekend inactive" do
|
710
|
+
before(:all) do
|
711
|
+
@time_zone = "Beijing"
|
712
|
+
Time.zone = @time_zone
|
713
|
+
Chronic.time_class = Time.zone
|
714
|
+
@day_partitions = "F"*24 + ("T"*12 + "F"*12) * 5 + "F"*24
|
715
|
+
end
|
716
|
+
context "when time is before ad's start_datetime, and date range is from Monday @ 9:30am to Friday @ 6pm" do
|
717
|
+
it "should return 5 days" do
|
718
|
+
@start = Chronic.parse("Monday @ 9:30am")
|
719
|
+
@end = Chronic.parse("Monday @ 6pm") + 4.days #Friday
|
720
|
+
remaining_days(Chronic.parse("1 month ago")).should == 5
|
721
|
+
end
|
722
|
+
end
|
723
|
+
context "when time is before ad's start_datetime, and date range is from Monday @ 6pm to Friday @ 9:30am" do
|
724
|
+
it "should return 4 days" do
|
725
|
+
@start = Chronic.parse("Monday @ 6pm")
|
726
|
+
@end = Chronic.parse("Monday @ 9:30am") + 4.days #Friday
|
727
|
+
remaining_days(Chronic.parse("1 month ago")).should == 4
|
728
|
+
end
|
729
|
+
end
|
730
|
+
context "when time is before ad's start_datetime, and date range is from Monday @ 6pm to Friday morning @ 12am" do
|
731
|
+
it "should return 4 days" do
|
732
|
+
@start = Chronic.parse("Monday @ 6pm")
|
733
|
+
@end = Chronic.parse("Monday @ 12am") + 4.days #Friday
|
734
|
+
remaining_days(Chronic.parse("1 month ago")).should == 3
|
735
|
+
end
|
736
|
+
end
|
737
|
+
context "when time is before ad's start_datetime, and date range is from Monday @ 6pm to Three Mondays later @ 6am" do
|
738
|
+
it "should return 15 hours" do
|
739
|
+
@start = Chronic.parse("Monday @ 6pm")
|
740
|
+
@end = Chronic.parse("Monday @ 6am") + 3.weeks
|
741
|
+
remaining_days(Chronic.parse("1 month ago")).should == 15
|
742
|
+
end
|
743
|
+
end
|
744
|
+
context "when the date range is really long" do
|
745
|
+
it "should not take a long time to compute" do
|
746
|
+
@start = Chronic.parse "2010-01-01 1am"
|
747
|
+
@end = Chronic.parse "2020-01-07 5am"
|
748
|
+
started = Time.now.to_f
|
749
|
+
remaining_days(Chronic.parse("2009-12-31 11pm")).should == 2613
|
750
|
+
finished = Time.now.to_f
|
751
|
+
(finished - started).should > 0.0
|
752
|
+
(finished - started).should < 0.1
|
753
|
+
end
|
754
|
+
end
|
755
|
+
end
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
759
|
+
describe ".remaining_hours(time)" do
|
760
|
+
context "when time is on or after ad's end_datetime" do
|
761
|
+
it "should return zero" do
|
762
|
+
@end = Time.at(1234567890)
|
763
|
+
a = FakeAd.new(:end_datetime => @end)
|
764
|
+
a.send(:remaining_hours, @end).should == 0
|
765
|
+
a.send(:remaining_hours, @end + 1.day).should == 0
|
766
|
+
end
|
767
|
+
end
|
768
|
+
context "when time is before ad's end_datetime" do
|
769
|
+
context "when ad is not day-partitioned" do
|
770
|
+
context "when time is before ad's start_datetime" do
|
771
|
+
it "returns the number of hours between the ad's start and end" do
|
772
|
+
@end = Time.at(1234567890)
|
773
|
+
a = FakeAd.new(:start_datetime => @end - 3.days, :end_datetime => @end, :day_partitions => nil)
|
774
|
+
a.send(:remaining_hours, @end - 5.days).should == 24*3
|
775
|
+
end
|
776
|
+
end
|
777
|
+
context "when time is after the ad's start_datetime" do
|
778
|
+
it "should return the number of hours between the given time and the ad's end" do
|
779
|
+
@end = Time.at(1234567890)
|
780
|
+
a = FakeAd.new(:start_datetime => @end - 5.days, :end_datetime => @end, :day_partitions => nil)
|
781
|
+
a.send(:remaining_hours, @end - 3.days).should == 24*3
|
782
|
+
end
|
783
|
+
end
|
784
|
+
end
|
785
|
+
context "when ad is partitioned with the first 12 hours of each day active, and the last 12 inactive" do
|
786
|
+
before(:all) do
|
787
|
+
@time_zone = "Beijing"
|
788
|
+
Time.zone = @time_zone
|
789
|
+
Chronic.time_class = Time.zone
|
790
|
+
@day_partitions = ("T"*12 + "F"*12) * 7
|
791
|
+
end
|
792
|
+
context "when time is before ad's start_datetime, and date range is from Monday @ 9:30am to Friday @ 6pm" do
|
793
|
+
it "should return 50.5 hours" do
|
794
|
+
a = FakeAd.new(:start_datetime => Chronic.parse("Monday @ 9:30am"),
|
795
|
+
:end_datetime => Chronic.parse("Monday @ 6pm") + 4.days,
|
796
|
+
:day_partitions => @day_partitions)
|
797
|
+
a.send(:remaining_hours, Chronic.parse("1 month ago")).should == 50.5
|
798
|
+
end
|
799
|
+
end
|
800
|
+
context "when time is before ad's start_datetime, and date range is from Monday @ 6pm to Friday @ 9:30am" do
|
801
|
+
it "should return 45.5 hours" do
|
802
|
+
a = FakeAd.new(:start_datetime => Chronic.parse("Monday @ 6pm"),
|
803
|
+
:end_datetime => Chronic.parse("Monday @ 9:30am") + 4.days,
|
804
|
+
:day_partitions => @day_partitions)
|
805
|
+
a.send(:remaining_hours, Chronic.parse("1 month ago")).should == 45.5
|
806
|
+
end
|
807
|
+
end
|
808
|
+
context "when time is before ad's start_datetime, and date range is from Monday @ 6pm to Three Mondays later @ 6am" do
|
809
|
+
it "should return 246 (12 * 7 * 3 - 6) hours" do
|
810
|
+
a = FakeAd.new(:start_datetime => Chronic.parse("Monday @ 6pm"),
|
811
|
+
:end_datetime => Chronic.parse("Monday @ 6am") + 3.weeks,
|
812
|
+
:day_partitions => @day_partitions)
|
813
|
+
a.send(:remaining_hours, Chronic.parse("1 month ago")).should == 12 * 7 * 3 - 6
|
814
|
+
end
|
815
|
+
end
|
816
|
+
context "when the date range is really long" do
|
817
|
+
it "should not take a long time to compute" do
|
818
|
+
a = FakeAd.new(:start_datetime => Chronic.parse("2010-01-01 1am"),
|
819
|
+
:end_datetime => Chronic.parse("2020-01-07 5am"),
|
820
|
+
:day_partitions => @day_partitions)
|
821
|
+
started = Time.now.to_f
|
822
|
+
a.send(:remaining_hours, Chronic.parse("2009-12-31 11pm")).should == 43900
|
823
|
+
finished = Time.now.to_f
|
824
|
+
(finished - started).should > 0.0
|
825
|
+
(finished - started).should < 0.1
|
826
|
+
end
|
827
|
+
end
|
828
|
+
end
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
def remaining_hours_today(time)
|
833
|
+
@ad = FakeAd.new(
|
834
|
+
:start_datetime => @start,
|
835
|
+
:end_datetime => @end,
|
836
|
+
:day_partitions => @day_partitions)
|
837
|
+
@ad.send(:remaining_hours_today, time.in_time_zone(@time_zone))
|
838
|
+
end
|
839
|
+
describe ".remaining_hours_today(time)" do
|
840
|
+
before(:all) do
|
841
|
+
@time_zone = "Beijing"
|
842
|
+
Time.zone = @time_zone
|
843
|
+
Chronic.time_class = Time.zone
|
844
|
+
end
|
845
|
+
context "when ad is not day-partitioned" do
|
846
|
+
before(:all) { @day_partitions = nil}
|
847
|
+
context "when ad's start and end date/time are the same day" do
|
848
|
+
before(:all) do
|
849
|
+
@start = Chronic.parse('tomorrow @ 9am')
|
850
|
+
@end = Chronic.parse('tomorrow @ 5pm')
|
851
|
+
end
|
852
|
+
context "when the given time is on a different day" do
|
853
|
+
it "should return zero" do
|
854
|
+
remaining_hours_today(@start - 2.days).should == 0
|
855
|
+
remaining_hours_today(@end - 2.days).should == 0
|
856
|
+
end
|
857
|
+
end
|
858
|
+
context "when the given time is on the same day, but before the start" do
|
859
|
+
it "should return the full number of hours of the ad's range" do
|
860
|
+
remaining_hours_today(@start - 5.hours).should == 8
|
861
|
+
end
|
862
|
+
end
|
863
|
+
context 'when the given time is on the same day, within the date range' do
|
864
|
+
it "should return the hours between the given time and the end" do
|
865
|
+
remaining_hours_today(Chronic.parse('tomorrow @ noon')).should == 5
|
866
|
+
end
|
867
|
+
end
|
868
|
+
context "when the given time is on the same day, but after the end" do
|
869
|
+
it "should return zero" do
|
870
|
+
remaining_hours_today(@end + 1.hour).should == 0
|
871
|
+
end
|
872
|
+
end
|
873
|
+
end
|
874
|
+
context "when the given time is at least 24 hours before the start time" do
|
875
|
+
it "should return zero" do
|
876
|
+
@start = Time.at(1234567890)
|
877
|
+
@end = @start + 1.month
|
878
|
+
remaining_hours_today(@start - 1.day).should == 0
|
879
|
+
end
|
880
|
+
end
|
881
|
+
context "when the given time is at least 24 hours after the end time" do
|
882
|
+
it "should return zero" do
|
883
|
+
@start = Time.at(1234567890)
|
884
|
+
@end = @start + 1.month
|
885
|
+
remaining_hours_today(@end + 1.day).should == 0
|
886
|
+
end
|
887
|
+
end
|
888
|
+
context "when the given time is after the the start_time" do
|
889
|
+
context "and at least 24 hours before the end time" do
|
890
|
+
it "should return the hours from the given time to midnight of that night" do
|
891
|
+
@start = Chronic.parse("yesterday @ 3am")
|
892
|
+
@time = Chronic.parse("today @ 8pm")
|
893
|
+
@end = @time + 1.day
|
894
|
+
remaining_hours_today(@time).should == 4
|
895
|
+
end
|
896
|
+
end
|
897
|
+
context "and the ad's end comes before midnight of the given time" do
|
898
|
+
context "and the given time is before the end time" do
|
899
|
+
it "should return the hours between the given time and the end time" do
|
900
|
+
@start = Chronic.parse("yesterday @ 3am")
|
901
|
+
@time = Chronic.parse("today @ 8pm")
|
902
|
+
@end = @time + 2.hours
|
903
|
+
remaining_hours_today(@time).should == 2
|
904
|
+
end
|
905
|
+
end
|
906
|
+
context "and the given time is after the end time" do
|
907
|
+
it "should return zero" do
|
908
|
+
@start = Chronic.parse("yesterday @ 3am")
|
909
|
+
@time = Chronic.parse("today @ 8pm")
|
910
|
+
@end = @time - 2.hours
|
911
|
+
remaining_hours_today(@time).should == 0
|
912
|
+
end
|
913
|
+
end
|
914
|
+
context "and the given time matches the end time" do
|
915
|
+
it "should return zero" do
|
916
|
+
@start = Chronic.parse("yesterday @ 3am")
|
917
|
+
@time = Chronic.parse("today @ 8pm")
|
918
|
+
@end = @time
|
919
|
+
remaining_hours_today(@time).should == 0
|
920
|
+
end
|
921
|
+
end
|
922
|
+
end
|
923
|
+
end
|
924
|
+
end
|
925
|
+
context "when ad is partitioned with the first 12 hours of each day active, and the last 12 inactive" do
|
926
|
+
before(:all) { @day_partitions = ("T"*12 + "F"*12) * 7}
|
927
|
+
context "when ad's start and end date/time are the same day" do
|
928
|
+
before(:all) do
|
929
|
+
@start = Chronic.parse('tomorrow @ 9am')
|
930
|
+
@end = Chronic.parse('tomorrow @ 5pm')
|
931
|
+
end
|
932
|
+
context "when the given time is on a different day" do
|
933
|
+
it "should return zero" do
|
934
|
+
remaining_hours_today(@start - 2.days).should == 0
|
935
|
+
remaining_hours_today(@end - 2.days).should == 0
|
936
|
+
end
|
937
|
+
end
|
938
|
+
context "when the given time is on the same day, but before the start" do
|
939
|
+
it "should return the full number of hours of the ad's range" do
|
940
|
+
remaining_hours_today(@start - 5.hours).should == 3
|
941
|
+
end
|
942
|
+
end
|
943
|
+
context 'when the given time is on the same day, within the date range' do
|
944
|
+
it "should return the hours between the given time and the end" do
|
945
|
+
remaining_hours_today(Chronic.parse('tomorrow @ 11am')).should == 1
|
946
|
+
end
|
947
|
+
end
|
948
|
+
context "when the given time is on the same day, but after the end" do
|
949
|
+
it "should return zero" do
|
950
|
+
remaining_hours_today(@end + 1.hour).should == 0
|
951
|
+
end
|
952
|
+
end
|
953
|
+
end
|
954
|
+
context "when the given time is at least 24 hours before the start time" do
|
955
|
+
it "should return zero" do
|
956
|
+
@start = Time.at(1234567890)
|
957
|
+
@end = @start + 1.month
|
958
|
+
remaining_hours_today(@start - 1.day).should == 0
|
959
|
+
end
|
960
|
+
end
|
961
|
+
context "when the given time is at least 24 hours after the end time" do
|
962
|
+
it "should return zero" do
|
963
|
+
@start = Time.at(1234567890)
|
964
|
+
@end = @start + 1.month
|
965
|
+
remaining_hours_today(@end + 1.day).should == 0
|
966
|
+
end
|
967
|
+
end
|
968
|
+
context "when the given time is after the the start_time" do
|
969
|
+
context "and at least 24 hours before the end time" do
|
970
|
+
it "should return the hours from the given time to midnight of that night" do
|
971
|
+
@start = Chronic.parse("yesterday @ 3am")
|
972
|
+
@time = Chronic.parse("today @ 7am")
|
973
|
+
@end = @time + 1.day
|
974
|
+
remaining_hours_today(@time).should == 5 #day part ends at noon
|
975
|
+
end
|
976
|
+
end
|
977
|
+
context "and the ad's end comes before midnight of the given time" do
|
978
|
+
context "and the given time is before the end time" do
|
979
|
+
it "should return the hours between the given time and the end time" do
|
980
|
+
@start = Chronic.parse("yesterday @ 3am")
|
981
|
+
@time = Chronic.parse("today @ 10:30am")
|
982
|
+
@end = @time + 2.hours
|
983
|
+
remaining_hours_today(@time).should == 1.5 #day part ends at noon
|
984
|
+
end
|
985
|
+
end
|
986
|
+
context "and the given time is after the end time" do
|
987
|
+
it "should return zero" do
|
988
|
+
@start = Chronic.parse("yesterday @ 3am")
|
989
|
+
@time = Chronic.parse("today @ 7am")
|
990
|
+
@end = @time - 2.hours
|
991
|
+
remaining_hours_today(@time).should == 0
|
992
|
+
end
|
993
|
+
end
|
994
|
+
context "and the given time matches the end time" do
|
995
|
+
it "should return zero" do
|
996
|
+
@start = Chronic.parse("yesterday @ 3am")
|
997
|
+
@time = Chronic.parse("today @ 7am")
|
998
|
+
@end = @time
|
999
|
+
remaining_hours_today(@time).should == 0
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
def beginning_of_week(time)
|
1008
|
+
FakeAd.new.send(:beginning_of_week, time)
|
1009
|
+
end
|
1010
|
+
describe ".beginning_of_week(time)" do
|
1011
|
+
context "time is on sunday morning at midnight" do
|
1012
|
+
it "should return an equivalent time" do
|
1013
|
+
time = Chronic.parse("Sunday @ 00:00")
|
1014
|
+
beginning_of_week(time).should == time
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
context "time is on sunday at noon" do
|
1018
|
+
it "should return a time 12 hours before" do
|
1019
|
+
time = Chronic.parse("Sunday @ noon")
|
1020
|
+
beginning_of_week(time).should == time - 12.hours
|
1021
|
+
end
|
1022
|
+
end
|
1023
|
+
context "time is on monday at noon" do
|
1024
|
+
it "should return a time 12 hours and one day before" do
|
1025
|
+
time = Chronic.parse("Monday @ noon")
|
1026
|
+
beginning_of_week(time).should == time - (12.hours + 1.day)
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
context "time is on tuesday at noon" do
|
1030
|
+
it "should return a time 12 hours and two days before" do
|
1031
|
+
time = Chronic.parse("Tuesday @ noon")
|
1032
|
+
beginning_of_week(time).should == time - (12.hours + 2.day)
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
context "time is on wednesday at noon" do
|
1036
|
+
it "should return a time 12 hours and three days before" do
|
1037
|
+
time = Chronic.parse("Wednesday @ noon")
|
1038
|
+
beginning_of_week(time).should == time - (12.hours + 3.day)
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
context "time is on thursday at noon" do
|
1042
|
+
it "should return a time 12 hours and four days before" do
|
1043
|
+
time = Chronic.parse("Thursday @ noon")
|
1044
|
+
beginning_of_week(time).should == time - (12.hours + 4.day)
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
context "time is on friday at noon" do
|
1048
|
+
it "should return a time 12 hours and five days before" do
|
1049
|
+
time = Chronic.parse("Friday @ noon")
|
1050
|
+
beginning_of_week(time).should == time - (12.hours + 5.day)
|
1051
|
+
end
|
1052
|
+
end
|
1053
|
+
context "time is on saturday at noon" do
|
1054
|
+
it "should return a time 12 hours and six days before" do
|
1055
|
+
time = Chronic.parse("Saturday @ noon")
|
1056
|
+
beginning_of_week(time).should == time - (12.hours + 6.day)
|
1057
|
+
end
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
def beginning_of_hour(time)
|
1062
|
+
FakeAd.new.send(:beginning_of_hour, time)
|
1063
|
+
end
|
1064
|
+
describe ".beginning_of_hour(time)" do
|
1065
|
+
before(:all) { @one_pm = Chronic.parse('1:00pm')}
|
1066
|
+
context "at 1:00 pm" do
|
1067
|
+
it "should return 1:00 pm" do
|
1068
|
+
beginning_of_hour(@one_pm).should == @one_pm
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
context "at 1:01 pm" do
|
1072
|
+
it "should return 1:00 pm" do
|
1073
|
+
beginning_of_hour(@one_pm + 1.minute).should == @one_pm
|
1074
|
+
end
|
1075
|
+
end
|
1076
|
+
context "at 1:59:59.9 pm" do
|
1077
|
+
it "should return 1:00 pm" do
|
1078
|
+
beginning_of_hour(@one_pm + 59.minutes + 59.9.seconds).should == @one_pm
|
1079
|
+
end
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
def partitioned_hours(beginning, ending)
|
1084
|
+
FakeAd.new(:day_partitions => @day_partitions).send(:partitioned_hours, beginning, ending)
|
1085
|
+
end
|
1086
|
+
describe ".partitioned_hours(beginning, ending)" do
|
1087
|
+
before(:all) do
|
1088
|
+
@time_zone = "Beijing"
|
1089
|
+
Time.zone = @time_zone
|
1090
|
+
Chronic.time_class = Time.zone
|
1091
|
+
end
|
1092
|
+
context "when ad is partitioned, active first 12 hours of every week day, inactive the last 12 hours and weekends" do
|
1093
|
+
before(:all) { @day_partitions = "F"*24 + ("T"*12 + "F"*12)*5 + "F"*24 }
|
1094
|
+
context "and beginning after ending" do
|
1095
|
+
it "should return zero" do
|
1096
|
+
partitioned_hours(Time.now, Time.now - 1.day).should == 0
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
context "and beginning and ending are in the same hour" do
|
1100
|
+
context "and that hour is an active partition" do
|
1101
|
+
it "should return the time (in hours) from beginning to ending" do
|
1102
|
+
@start = Chronic.parse("Monday @ 9:10am")
|
1103
|
+
@end = Chronic.parse("Monday @ 9:50am")
|
1104
|
+
partitioned_hours(@start, @end).should == 40/60.0
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
context "and that hour is an inactive partition" do
|
1108
|
+
it "should return zero" do
|
1109
|
+
@start = Chronic.parse("Monday @ 9:10pm")
|
1110
|
+
@end = Chronic.parse("Monday @ 9:50pm")
|
1111
|
+
partitioned_hours(@start, @end).should == 0
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
end
|
1115
|
+
it "should have more specs"
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
describe ".partitioned_days(beginning, ending)" do
|
1120
|
+
it "should have specs"
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
describe ".each_day(beginning, ending)" do
|
1124
|
+
it "should have specs"
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
describe ".each_hour(beginning, ending)" do
|
1128
|
+
it "should have specs"
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
describe ".actual_hours(beginning, ending)" do
|
1132
|
+
it "should have specs"
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
describe ".days_per_week" do
|
1136
|
+
it "should have specs"
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
describe ".hours_per_week" do
|
1140
|
+
it "should have specs"
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
end
|
1144
|
+
end
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
|