mailcannon 0.0.8 → 0.1.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +1 -2
- data/Rakefile +1 -1
- data/Readme.md +39 -4
- data/env.sample.sh +5 -4
- data/lib/mailcannon/adapters/sendgrid_web.rb +48 -12
- data/lib/mailcannon/envelope.rb +12 -3
- data/lib/mailcannon/envelope_bag.rb +9 -0
- data/lib/mailcannon/envelope_bag_map_reduce.rb +106 -0
- data/lib/mailcannon/envelope_bag_statistic.rb +5 -0
- data/lib/mailcannon/reduces/envelope_bag_map.js +3 -0
- data/lib/mailcannon/reduces/envelope_bag_reduce.js +116 -0
- data/lib/mailcannon/sendgrid_event.rb +18 -0
- data/lib/mailcannon/version.rb +4 -3
- data/lib/mailcannon/workers/barrel.rb +2 -16
- data/lib/mailcannon/workers/envelope_bag_reduce_job.rb +11 -0
- data/lib/mailcannon.rb +5 -5
- data/mailcannon.gemspec +0 -2
- data/spec/integration/1k_spec.rb +16 -2
- data/spec/integration/full_stack_stats_spec.rb +118 -0
- data/spec/integration/xsmtpapi_spec.rb +4 -1
- data/spec/mailcannon/adapters/sendgrid_spec.rb +17 -4
- data/spec/mailcannon/envelope_bag_map_reduce_spec.rb +211 -0
- data/spec/mailcannon/envelope_bag_statistic_spec.rb +41 -0
- data/spec/mailcannon/envelope_spec.rb +5 -0
- data/spec/mailcannon/workers/envelope_bag_reduce_job_spec.rb +17 -0
- data/templates/config/mailcannon.yml +10 -1
- metadata +18 -38
- data/lib/mailcannon/airbrake.rb +0 -18
- data/lib/mailcannon/librato.rb +0 -15
- data/spec/mailcannon/airbrake_spec.rb +0 -23
- data/spec/mailcannon/librato_spec.rb +0 -24
@@ -0,0 +1,118 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe 'full stack test' do
|
4
|
+
describe "should send 2 envelopes, receive correct statistics and map/reduce data correctly" do
|
5
|
+
|
6
|
+
let(:expected_hash_a){
|
7
|
+
{
|
8
|
+
"posted"=>{"count"=>1.0, "targets"=>["1"]},
|
9
|
+
"processed"=>{"count"=>1.0, "targets"=>["2"]},
|
10
|
+
"delivered"=>{"count"=>1.0, "targets"=>["3"]},
|
11
|
+
"open" => {"count"=>1.0, "targets"=>["4"]},
|
12
|
+
"click"=>{"count"=>1.0, "targets"=>["5"]},
|
13
|
+
"deferred"=>{"count"=>1.0, "targets"=>["6"]},
|
14
|
+
"spam_report"=>{"count"=>1.0, "targets"=>["7"]},
|
15
|
+
"spam"=>{"count"=>1.0, "targets"=>["8"]},
|
16
|
+
"unsubscribe"=>{"count"=>1.0, "targets"=>["9"]},
|
17
|
+
"drop"=>{"count"=>1.0, "targets"=>["10"]},
|
18
|
+
"hard_bounce" => {"count"=>1.0, "targets"=>["11"]},
|
19
|
+
"soft_bounce"=>{"count"=>1.0, "targets"=>["12"]},
|
20
|
+
"unknown"=>{"count"=>1.0, "targets"=>["13"]}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
let(:expected_hash_b){
|
25
|
+
{
|
26
|
+
"posted"=>{"count"=>2.0, "targets"=>["1", "1" ]},
|
27
|
+
"processed"=>{"count"=>2.0, "targets"=>["2", "2"]},
|
28
|
+
"delivered"=>{"count"=>2.0, "targets"=>["3", "3"]},
|
29
|
+
"open" => {"count"=>2.0, "targets"=>["4", "4"]},
|
30
|
+
"click"=>{"count"=>2.0, "targets"=>["5","5"]},
|
31
|
+
"deferred"=>{"count"=>2.0, "targets"=>["6", "6"]},
|
32
|
+
"spam_report"=>{"count"=>2.0, "targets"=>["7", "7"]},
|
33
|
+
"spam"=>{"count"=>2.0, "targets"=>["8", "8"]},
|
34
|
+
"unsubscribe"=>{"count"=>2.0, "targets"=>["9", "9"]},
|
35
|
+
"drop"=>{"count"=>2.0, "targets"=>["10", "10"]},
|
36
|
+
"hard_bounce" => {"count"=>2.0, "targets"=>["11", "11"]},
|
37
|
+
"soft_bounce"=>{"count"=>2.0, "targets"=>["12", "12"]},
|
38
|
+
"unknown"=>{"count"=>2.0, "targets"=>["13", "13"]}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
context "send emails and map reduce" do
|
43
|
+
let(:envelope_bag) { build(:empty_envelope_bag)}
|
44
|
+
let!(:envelope_a) { build(:envelope_multi) }
|
45
|
+
let!(:envelope_b) { build(:envelope_multi) }
|
46
|
+
|
47
|
+
it "sends http request for Sendgrid web API" do
|
48
|
+
envelope_bag.save
|
49
|
+
envelope_bag.envelopes << envelope_a
|
50
|
+
envelope_bag.envelopes << envelope_b
|
51
|
+
VCR.use_cassette('mailcannon_adapter_sendgrid_send_bulk') do
|
52
|
+
Sidekiq::Testing.inline! do
|
53
|
+
bm = Benchmark.measure do
|
54
|
+
envelope_a.send_bulk!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
envelope_a_hash = [
|
60
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'posted', target_id: '1'},
|
61
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'processed', target_id: '2'},
|
62
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'delivered', target_id: '3'},
|
63
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'open', target_id: '4'},
|
64
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'click', type: 'bounce', target_id: '5'},
|
65
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'deferred', type: 'expected', target_id: '6'},
|
66
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'spam_report', target_id: '7'},
|
67
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'spam', target_id: '8'},
|
68
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'unsubscribe', target_id: '9'},
|
69
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'drop', target_id: '10'},
|
70
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'bounce', type: 'bounce', target_id: '11'},
|
71
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'bounce', type: 'expected', target_id: '12'},
|
72
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'gfyigad', target_id: '13'},
|
73
|
+
]
|
74
|
+
MailCannon::SendgridEvent.insert_bulk(envelope_a_hash)
|
75
|
+
|
76
|
+
Sidekiq::Testing.inline! do
|
77
|
+
MailCannon::EnvelopeBagReduceJob.perform_async([envelope_bag.id])
|
78
|
+
end
|
79
|
+
|
80
|
+
expect(envelope_a.reload.sendgrid_events.where(processed: true).count).to eq(13)
|
81
|
+
expect(envelope_bag.stats).to eq(expected_hash_a)
|
82
|
+
|
83
|
+
VCR.use_cassette('mailcannon_adapter_sendgrid_send_bulk') do
|
84
|
+
Sidekiq::Testing.inline! do
|
85
|
+
bm = Benchmark.measure do
|
86
|
+
envelope_b.send_bulk!
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
envelope_b_hash = [
|
92
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'posted', target_id: '1'},
|
93
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'processed', target_id: '2'},
|
94
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'delivered', target_id: '3'},
|
95
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'open', target_id: '4'},
|
96
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'click', type: 'bounce', target_id: '5'},
|
97
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'deferred', type: 'expected', target_id: '6'},
|
98
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'spam_report', target_id: '7'},
|
99
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'spam', target_id: '8'},
|
100
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'unsubscribe', target_id: '9'},
|
101
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'drop', target_id: '10'},
|
102
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'bounce', type: 'bounce', target_id: '11'},
|
103
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'bounce', type: 'expected', target_id: '12'},
|
104
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'gfyigad', target_id: '13'},
|
105
|
+
]
|
106
|
+
MailCannon::SendgridEvent.insert_bulk(envelope_b_hash)
|
107
|
+
|
108
|
+
Sidekiq::Testing.inline! do
|
109
|
+
MailCannon::EnvelopeBagReduceJob.perform_async([envelope_bag.id])
|
110
|
+
end
|
111
|
+
|
112
|
+
expect(envelope_b.reload.sendgrid_events.where(processed: true).count).to eq(13)
|
113
|
+
expect(envelope_bag.stats).to eq(expected_hash_b)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -5,10 +5,13 @@ describe 'X-SMTPAPI compatibility' do
|
|
5
5
|
# This should guarantee the expected behavior for the following api:
|
6
6
|
# http://sendgrid.com/docs/API_Reference/SMTP_API/unique_arguments.html
|
7
7
|
context "generates expected xsmtpapi for #post!" do
|
8
|
+
let(:envelope_bag) { build(:empty_envelope_bag)}
|
8
9
|
let(:envelope) { build(:envelope_multi, xsmtpapi: { "sub" => { "-email-id-" => ["314159","271828"] }, "unique_args" => { "email_id" => "-email-id-"} }) }
|
9
|
-
let(:expectated_hash) { {"sub"=>{"-email-id-"=>["314159", "271828"], "*|NAME|*"=>["Mail Cannon", "Lucas Martins", "Contact"]}, "unique_args"=>{"email_id"=>"-email-id-"}, "to"=>["mailcannon@railsnapraia.com", "lucasmartins@railsnapraia.com", "contact@railsonthebeach.com"]} }
|
10
|
+
let(:expectated_hash) { {"sub"=>{"-email-id-"=>["314159", "271828"], "*|NAME|*"=>["Mail Cannon", "Lucas Martins", "Contact"], "*|EMAIL|*"=>["mailcannon@railsnapraia.com", "lucasmartins@railsnapraia.com", "contact@railsonthebeach.com"]}, "unique_args"=>{"email_id"=>"-email-id-", "envelope_id"=>envelope.id, "envelope_bag_id"=>envelope_bag.id}, "to"=>["mailcannon@railsnapraia.com", "lucasmartins@railsnapraia.com", "contact@railsonthebeach.com"]} }
|
10
11
|
|
11
12
|
it "returns true" do
|
13
|
+
envelope_bag.save
|
14
|
+
envelope_bag.envelopes << envelope
|
12
15
|
VCR.use_cassette('mailcannon_adapter_sendgrid_send_bulk') do
|
13
16
|
Sidekiq::Testing.inline! do
|
14
17
|
envelope.post!
|
@@ -21,13 +21,19 @@ describe MailCannon::Adapter::SendgridWeb do
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
describe "#send!" do
|
24
|
+
let(:envelope_bag) { build(:empty_envelope_bag) }
|
24
25
|
let(:envelope) { build(:envelope) }
|
26
|
+
|
25
27
|
it "sends http request for Sendgrid web API" do
|
28
|
+
envelope_bag.save
|
29
|
+
envelope_bag.envelopes << envelope
|
26
30
|
VCR.use_cassette('mailcannon_adapter_sendgrid_send') do
|
27
31
|
expect(envelope.send!).to be_true
|
28
32
|
end
|
29
33
|
end
|
30
34
|
it "calls after_sent callback" do
|
35
|
+
envelope_bag.save
|
36
|
+
envelope_bag.envelopes << envelope
|
31
37
|
VCR.use_cassette('mailcannon_adapter_sendgrid_send') do
|
32
38
|
envelope.should_receive(:after_sent)
|
33
39
|
envelope.send!
|
@@ -36,8 +42,11 @@ describe MailCannon::Adapter::SendgridWeb do
|
|
36
42
|
end
|
37
43
|
|
38
44
|
describe "#send_bulk!" do
|
45
|
+
let(:envelope_bag) { build(:empty_envelope_bag) }
|
39
46
|
let(:envelope) { build(:envelope_multi) }
|
40
47
|
it "sends http request for Sendgrid web API" do
|
48
|
+
envelope_bag.save
|
49
|
+
envelope_bag.envelopes << envelope
|
41
50
|
VCR.use_cassette('mailcannon_adapter_sendgrid_send_bulk') do
|
42
51
|
expect(envelope.send_bulk!).to be_true
|
43
52
|
end
|
@@ -46,12 +55,16 @@ describe MailCannon::Adapter::SendgridWeb do
|
|
46
55
|
|
47
56
|
context "grab auth exception" do
|
48
57
|
describe "#send!" do
|
58
|
+
let(:envelope_bag) { build(:empty_envelope_bag) }
|
49
59
|
let(:envelope) { build(:envelope_wrong_auth) }
|
50
|
-
|
51
|
-
|
52
|
-
|
60
|
+
it "sends http request for Sendgrid web API with a wrong user/passwd combination" do
|
61
|
+
envelope_bag.save
|
62
|
+
envelope_bag.envelopes << envelope
|
63
|
+
Sidekiq::Testing.inline! do
|
64
|
+
expect{envelope.send!}.to raise_error(MailCannon::Adapter::AuthException)
|
65
|
+
end
|
53
66
|
end
|
54
67
|
end
|
55
68
|
end
|
56
|
-
|
69
|
+
|
57
70
|
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe MailCannon::EnvelopeBagMapReduce do
|
4
|
+
|
5
|
+
describe "#find_gem_root_path" do
|
6
|
+
|
7
|
+
after(:all) do
|
8
|
+
Gem::Specification.any_instance.unstub(:gem_dir)
|
9
|
+
Gem::Specification.unstub(:find_by_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when gem is found" do
|
13
|
+
it "returns the path" do
|
14
|
+
mocked_gem_path = "/app/vendor/bundle/ruby/2.0.0/bundler/gems/mailcannon-2c266138c2eb"
|
15
|
+
Gem::Specification.stub(:find_by_name).and_return(Gem::Specification.new)
|
16
|
+
Gem::Specification.any_instance.stub(:gem_dir).and_return(mocked_gem_path)
|
17
|
+
expect(MailCannon::EnvelopeBag.find_gem_root_path).to eq(mocked_gem_path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when gem is not found" do
|
22
|
+
it "returns empty string" do
|
23
|
+
Gem::Specification.stub(:find_by_name) do
|
24
|
+
raise LoadError.new(message: /mailcannon/)
|
25
|
+
end
|
26
|
+
expect(MailCannon::EnvelopeBag.find_gem_root_path).to eq("")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when dependence is not found" do
|
31
|
+
it "raises error" do
|
32
|
+
Gem::Specification.stub(:find_by_name) do
|
33
|
+
raise LoadError.new(message: /some_other_gem/)
|
34
|
+
end
|
35
|
+
expect{ MailCannon::EnvelopeBag.find_gem_root_path }.to raise_error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "something else goes wrong" do
|
40
|
+
it "raises exception" do
|
41
|
+
Gem::Specification.stub(:find_by_name).and_raise(Exception.new)
|
42
|
+
expect{ MailCannon::EnvelopeBag.find_gem_root_path }.to raise_exception
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#js_map" do
|
48
|
+
|
49
|
+
before(:each) do
|
50
|
+
File.stub(:read) { |path| path }
|
51
|
+
MailCannon::EnvelopeBag.instance_variable_set(:@js_map, nil)
|
52
|
+
end
|
53
|
+
|
54
|
+
after(:each) do
|
55
|
+
File.unstub(:read)
|
56
|
+
MailCannon::EnvelopeBag.unstub(:find_gem_root_path)
|
57
|
+
MailCannon::EnvelopeBag.instance_variable_set(:@js_map, nil)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
context "when gem is found" do
|
62
|
+
it "returns full gem file path" do
|
63
|
+
MailCannon::EnvelopeBag.stub(:find_gem_root_path).and_return("path/to/gem")
|
64
|
+
expect(MailCannon::EnvelopeBag.js_map).to eq("path/to/gem/lib/mailcannon/reduces/envelope_bag_map.js")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when gem is not found" do
|
69
|
+
it "returns file path relative to current location" do
|
70
|
+
MailCannon::EnvelopeBag.stub(:find_gem_root_path).and_return("")
|
71
|
+
expect(MailCannon::EnvelopeBag.js_map).to eq("lib/mailcannon/reduces/envelope_bag_map.js")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#js_reduce" do
|
77
|
+
|
78
|
+
before(:each) do
|
79
|
+
File.stub(:read) { |path| path }
|
80
|
+
MailCannon::EnvelopeBag.instance_variable_set(:@js_reduce, nil)
|
81
|
+
end
|
82
|
+
|
83
|
+
after(:each) do
|
84
|
+
File.unstub(:read)
|
85
|
+
MailCannon::EnvelopeBag.unstub(:find_gem_root_path)
|
86
|
+
MailCannon::EnvelopeBag.instance_variable_set(:@js_reduce, nil)
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when gem is found" do
|
90
|
+
it "returns full gem file path" do
|
91
|
+
MailCannon::EnvelopeBag.stub(:find_gem_root_path).and_return("path/to/gem")
|
92
|
+
expect(MailCannon::EnvelopeBag.js_reduce).to eq("path/to/gem/lib/mailcannon/reduces/envelope_bag_reduce.js")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "when gem is not found" do
|
97
|
+
it "returns file path relative to current location" do
|
98
|
+
MailCannon::EnvelopeBag.stub(:find_gem_root_path).and_return("")
|
99
|
+
expect(MailCannon::EnvelopeBag.js_reduce).to eq("lib/mailcannon/reduces/envelope_bag_reduce.js")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
let!(:envelope_bag) { build(:empty_envelope_bag) }
|
106
|
+
let(:envelope_a) { build(:envelope, envelope_bag_id: envelope_bag.id) }
|
107
|
+
let(:envelope_b) { build(:envelope, envelope_bag_id: envelope_bag.id) }
|
108
|
+
let(:envelope_c) { build(:envelope, envelope_bag_id: envelope_bag.id) }
|
109
|
+
|
110
|
+
def insert_sample_events
|
111
|
+
test_hash = [
|
112
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'delivered', target_id: '1'},
|
113
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'open', target_id: '2'},
|
114
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'bounce', type: 'bounce', target_id: '3'},
|
115
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'delivered', target_id: '1'},
|
116
|
+
{envelope_id: envelope_b.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'click', target_id: '2'},
|
117
|
+
{envelope_id: envelope_c.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'click', target_id: '1'}
|
118
|
+
]
|
119
|
+
MailCannon::SendgridEvent.insert_bulk(test_hash)
|
120
|
+
end
|
121
|
+
|
122
|
+
before(:each) do
|
123
|
+
envelope_a.save
|
124
|
+
envelope_b.save
|
125
|
+
envelope_c.save
|
126
|
+
insert_sample_events
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#change_events_status_for_envelope" do
|
130
|
+
it "sets events status (processed) to :lock(false)" do
|
131
|
+
MailCannon::EnvelopeBag.change_events_status_for_envelope_bag(envelope_bag.id, nil, :lock)
|
132
|
+
expect(envelope_a.reload.sendgrid_events.where(processed: false).count).to eq(3)
|
133
|
+
expect(envelope_b.reload.sendgrid_events.where(processed: false).count).to eq(2)
|
134
|
+
expect(envelope_c.reload.sendgrid_events.where(processed: false).count).to eq(1)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "sets events status (processed) to :processed(true)" do
|
138
|
+
MailCannon::EnvelopeBag.change_events_status_for_envelope_bag(envelope_bag.id, nil, :processed)
|
139
|
+
expect(envelope_a.reload.sendgrid_events.where(processed: true).count).to eq(3)
|
140
|
+
expect(envelope_b.reload.sendgrid_events.where(processed: true).count).to eq(2)
|
141
|
+
expect(envelope_c.reload.sendgrid_events.where(processed: true).count).to eq(1)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe ".reduce_statistics_for_envelope_bag" do
|
146
|
+
let(:expected_hash_a){
|
147
|
+
{
|
148
|
+
"posted"=>{"count"=>0.0, "targets"=>[]},
|
149
|
+
"processed"=>{"count"=>0.0, "targets"=>[]},
|
150
|
+
"delivered"=>{"count"=>2.0, "targets"=>["1", "1"]},
|
151
|
+
"open"=>{"count"=>1.0, "targets"=>["2"]},
|
152
|
+
"click"=>{"count"=>2.0, "targets"=>["2", "1"]},
|
153
|
+
"deferred"=>{"count"=>0.0, "targets"=>[]},
|
154
|
+
"spam_report"=>{"count"=>0.0, "targets"=>[]},
|
155
|
+
"spam"=>{"count"=>0.0, "targets"=>[]},
|
156
|
+
"unsubscribe"=>{"count"=>0.0, "targets"=>[]},
|
157
|
+
"drop"=>{"count"=>0.0, "targets"=>[]},
|
158
|
+
"hard_bounce"=>{"count"=>1.0, "targets"=>["3"]},
|
159
|
+
"soft_bounce"=>{"count"=>0.0, "targets"=>[]},
|
160
|
+
"unknown"=>{"count"=>0.0, "targets"=>[]}
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
let(:expected_hash_b){
|
165
|
+
{
|
166
|
+
"posted"=>{"count"=>0.0, "targets"=>[]},
|
167
|
+
"processed"=>{"count"=>0.0, "targets"=>[]},
|
168
|
+
"delivered"=>{"count"=>4.0, "targets"=>["1", "1","1", "1"]},
|
169
|
+
"open"=>{"count"=>2.0, "targets"=>["2", "2"]},
|
170
|
+
"click"=>{"count"=>4.0, "targets"=>["2", "1","2", "1"]},
|
171
|
+
"deferred"=>{"count"=>0.0, "targets"=>[]},
|
172
|
+
"spam_report"=>{"count"=>0.0, "targets"=>[]},
|
173
|
+
"spam"=>{"count"=>0.0, "targets"=>[]},
|
174
|
+
"unsubscribe"=>{"count"=>0.0, "targets"=>[]},
|
175
|
+
"drop"=>{"count"=>0.0, "targets"=>[]},
|
176
|
+
"hard_bounce"=>{"count"=>2.0, "targets"=>["3","3"]},
|
177
|
+
"soft_bounce"=>{"count"=>0.0, "targets"=>[]},
|
178
|
+
"unknown"=>{"count"=>0.0, "targets"=>[]}
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
it "measure reduce output availability" do
|
183
|
+
envelope_bag.reduce_statistics
|
184
|
+
start_time = Time.now
|
185
|
+
while MailCannon::EnvelopeBagStatistic.count==0
|
186
|
+
sleep 1
|
187
|
+
end
|
188
|
+
end_time = Time.now
|
189
|
+
diff = end_time-start_time
|
190
|
+
expect(diff<1.0).to be_true
|
191
|
+
end
|
192
|
+
|
193
|
+
it "creates an EnvelopeStatistic entry" do
|
194
|
+
expect{ MailCannon::EnvelopeBag.reduce_statistics_for_envelope_bag(envelope_bag.id) }.to change{ MailCannon::EnvelopeBagStatistic.count }.from(0).to(1)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "returns statistics hash/json" do
|
198
|
+
envelope_bag.reduce_statistics
|
199
|
+
expect(envelope_bag.stats).to eq(expected_hash_a)
|
200
|
+
end
|
201
|
+
|
202
|
+
it "merges recurring reduces" do
|
203
|
+
envelope_bag.reduce_statistics
|
204
|
+
expect(envelope_bag.stats).to eq(expected_hash_a)
|
205
|
+
insert_sample_events
|
206
|
+
envelope_bag.reduce_statistics
|
207
|
+
expect(envelope_bag.stats).to eq(expected_hash_b)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe MailCannon::EnvelopeBagStatistic do
|
4
|
+
|
5
|
+
let!(:envelope_bag) { build(:empty_envelope_bag) }
|
6
|
+
let!(:envelope_a) { build(:envelope, envelope_bag_id: envelope_bag.id) }
|
7
|
+
|
8
|
+
def insert_sample_events
|
9
|
+
test_hash = [
|
10
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo1@bar.com', timestamp: 1322000092, unique_arg: 'my unique arg', event: 'delivered', target_id: '1'},
|
11
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo2@bar.com', timestamp: 1322000093, unique_arg: 'my unique arg', event: 'open', target_id: '2'},
|
12
|
+
{envelope_id: envelope_a.id, envelope_bag_id: envelope_bag.id, email: 'foo3@bar.com', timestamp: 1322000094, unique_arg: 'my unique arg', event: 'bounce', target_id: '3'},
|
13
|
+
]
|
14
|
+
MailCannon::SendgridEvent.insert_bulk(test_hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
envelope_bag.save
|
19
|
+
envelope_a.save
|
20
|
+
insert_sample_events
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "stats" do
|
24
|
+
it "has expected keys" do
|
25
|
+
envelope_bag.reduce_statistics
|
26
|
+
expect(envelope_bag.stats).to have_key("posted")
|
27
|
+
expect(envelope_bag.stats).to have_key("processed")
|
28
|
+
expect(envelope_bag.stats).to have_key("delivered")
|
29
|
+
expect(envelope_bag.stats).to have_key("open")
|
30
|
+
expect(envelope_bag.stats).to have_key("click")
|
31
|
+
expect(envelope_bag.stats).to have_key("deferred")
|
32
|
+
expect(envelope_bag.stats).to have_key("spam_report")
|
33
|
+
expect(envelope_bag.stats).to have_key("spam")
|
34
|
+
expect(envelope_bag.stats).to have_key("unsubscribe")
|
35
|
+
expect(envelope_bag.stats).to have_key("drop")
|
36
|
+
expect(envelope_bag.stats).to have_key("soft_bounce")
|
37
|
+
expect(envelope_bag.stats).to have_key("hard_bounce")
|
38
|
+
expect(envelope_bag.stats).to have_key("unknown")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -73,10 +73,13 @@ describe MailCannon::Envelope do
|
|
73
73
|
|
74
74
|
describe "xsmtpapi" do
|
75
75
|
context "keep xsmtpapi arguments after #post!" do
|
76
|
+
let(:envelope_bag) { build(:empty_envelope_bag)}
|
76
77
|
let(:envelope) { build(:envelope_multi, xsmtpapi: { "unique_args" => { "userid" => "1123", "template" => "welcome" }}) }
|
77
78
|
let(:name_placeholder) { MailCannon.config['default_name_placeholder'].to_s }
|
78
79
|
|
79
80
|
it "returns true" do
|
81
|
+
envelope_bag.save
|
82
|
+
envelope_bag.envelopes << envelope
|
80
83
|
VCR.use_cassette('mailcannon_adapter_sendgrid_send_bulk') do
|
81
84
|
Sidekiq::Testing.inline! do
|
82
85
|
envelope.post!
|
@@ -84,6 +87,8 @@ describe MailCannon::Envelope do
|
|
84
87
|
end
|
85
88
|
envelope.reload # content is changed inside the Adapter module
|
86
89
|
expect(envelope.xsmtpapi).to have_key("unique_args")
|
90
|
+
expect(envelope.xsmtpapi["unique_args"]).to have_key("envelope_id") if MailCannon.config['add_envelope_id_to_unique_args']
|
91
|
+
expect(envelope.xsmtpapi["unique_args"]).to have_key("envelope_bag_id") if MailCannon.config['add_envelope_bag_id_to_unique_args']
|
87
92
|
expect(envelope.xsmtpapi).to have_key("to")
|
88
93
|
expect(envelope.xsmtpapi).to have_key("sub")
|
89
94
|
expect(envelope.xsmtpapi['sub']).to have_key("*|NAME|*")
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe MailCannon::EnvelopeBagReduceJob do
|
4
|
+
describe "perform" do
|
5
|
+
|
6
|
+
let(:bag_1) { create(:filled_envelope_bag) }
|
7
|
+
let(:bag_2) { create(:filled_envelope_bag) }
|
8
|
+
|
9
|
+
it "calls the reduce trigger for each envelope" do
|
10
|
+
Sidekiq::Testing.inline! do
|
11
|
+
MailCannon::EnvelopeBag.should_receive(:reduce_statistics_for_envelope_bag).with(bag_2.id.to_s).and_return(nil)
|
12
|
+
MailCannon::EnvelopeBag.should_receive(:reduce_statistics_for_envelope_bag).with(bag_1.id.to_s).and_return(nil)
|
13
|
+
MailCannon::EnvelopeBagReduceJob.perform_async([bag_2.id, bag_1.id])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -11,4 +11,13 @@ waiting_time: 0
|
|
11
11
|
auto_destroy: true
|
12
12
|
|
13
13
|
# MailCannon builds the 'name' substitution Array for convenience, using the name from Envelope.to: [{email: 'foo', name: 'bar'}]
|
14
|
-
default_name_placeholder: "*|NAME|*"
|
14
|
+
default_name_placeholder: "*|NAME|*"
|
15
|
+
|
16
|
+
# MailCannon builds the 'email' substitution Array for convenience, using the email from Envelope.to: [{email: 'foo', name: 'bar'}]
|
17
|
+
default_email_placeholder: "*|EMAIL|*"
|
18
|
+
|
19
|
+
# Pretty self explanatory. This is intended to be used by your event consuming service.
|
20
|
+
add_envelope_id_to_unique_args: true
|
21
|
+
|
22
|
+
# Pretty self explanatory. This is intended to be used by your event consuming service.
|
23
|
+
add_envelope_bag_id_to_unique_args: true
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mailcannon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.1.0.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lucas Martins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -94,34 +94,6 @@ dependencies:
|
|
94
94
|
- - '>='
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: librato-metrics
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - '>='
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
type: :runtime
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - '>='
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: airbrake
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - '>='
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :runtime
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - '>='
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
97
|
- !ruby/object:Gem::Dependency
|
126
98
|
name: vcr
|
127
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -306,16 +278,20 @@ files:
|
|
306
278
|
- lib/mailcannon.rb
|
307
279
|
- lib/mailcannon/adapter.rb
|
308
280
|
- lib/mailcannon/adapters/sendgrid_web.rb
|
309
|
-
- lib/mailcannon/airbrake.rb
|
310
281
|
- lib/mailcannon/envelope.rb
|
311
282
|
- lib/mailcannon/envelope_bag.rb
|
283
|
+
- lib/mailcannon/envelope_bag_map_reduce.rb
|
284
|
+
- lib/mailcannon/envelope_bag_statistic.rb
|
312
285
|
- lib/mailcannon/event.rb
|
313
286
|
- lib/mailcannon/hash.rb
|
314
|
-
- lib/mailcannon/librato.rb
|
315
287
|
- lib/mailcannon/mail.rb
|
288
|
+
- lib/mailcannon/reduces/envelope_bag_map.js
|
289
|
+
- lib/mailcannon/reduces/envelope_bag_reduce.js
|
290
|
+
- lib/mailcannon/sendgrid_event.rb
|
316
291
|
- lib/mailcannon/stamp.rb
|
317
292
|
- lib/mailcannon/version.rb
|
318
293
|
- lib/mailcannon/workers/barrel.rb
|
294
|
+
- lib/mailcannon/workers/envelope_bag_reduce_job.rb
|
319
295
|
- mailcannon.gemspec
|
320
296
|
- spec/factories/envelope.rb
|
321
297
|
- spec/factories/envelope_bag.rb
|
@@ -324,14 +300,16 @@ files:
|
|
324
300
|
- spec/fixtures/cassettes/mailcannon_adapter_sendgrid_send_bulk.yml
|
325
301
|
- spec/fixtures/cassettes/mailcannon_integration_1k.yml
|
326
302
|
- spec/integration/1k_spec.rb
|
303
|
+
- spec/integration/full_stack_stats_spec.rb
|
327
304
|
- spec/integration/xsmtpapi_spec.rb
|
328
305
|
- spec/mailcannon/adapters/sendgrid_spec.rb
|
329
|
-
- spec/mailcannon/
|
306
|
+
- spec/mailcannon/envelope_bag_map_reduce_spec.rb
|
330
307
|
- spec/mailcannon/envelope_bag_spec.rb
|
308
|
+
- spec/mailcannon/envelope_bag_statistic_spec.rb
|
331
309
|
- spec/mailcannon/envelope_spec.rb
|
332
|
-
- spec/mailcannon/librato_spec.rb
|
333
310
|
- spec/mailcannon/stamp_spec.rb
|
334
311
|
- spec/mailcannon/workers/barrel_spec.rb
|
312
|
+
- spec/mailcannon/workers/envelope_bag_reduce_job_spec.rb
|
335
313
|
- spec/mailcannon_spec.rb
|
336
314
|
- spec/spec_helper.rb
|
337
315
|
- spec/support/mongoid.yml
|
@@ -352,9 +330,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
352
330
|
version: 1.9.3
|
353
331
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
354
332
|
requirements:
|
355
|
-
- - '
|
333
|
+
- - '>'
|
356
334
|
- !ruby/object:Gem::Version
|
357
|
-
version:
|
335
|
+
version: 1.3.1
|
358
336
|
requirements: []
|
359
337
|
rubyforge_project:
|
360
338
|
rubygems_version: 2.0.14
|
@@ -369,14 +347,16 @@ test_files:
|
|
369
347
|
- spec/fixtures/cassettes/mailcannon_adapter_sendgrid_send_bulk.yml
|
370
348
|
- spec/fixtures/cassettes/mailcannon_integration_1k.yml
|
371
349
|
- spec/integration/1k_spec.rb
|
350
|
+
- spec/integration/full_stack_stats_spec.rb
|
372
351
|
- spec/integration/xsmtpapi_spec.rb
|
373
352
|
- spec/mailcannon/adapters/sendgrid_spec.rb
|
374
|
-
- spec/mailcannon/
|
353
|
+
- spec/mailcannon/envelope_bag_map_reduce_spec.rb
|
375
354
|
- spec/mailcannon/envelope_bag_spec.rb
|
355
|
+
- spec/mailcannon/envelope_bag_statistic_spec.rb
|
376
356
|
- spec/mailcannon/envelope_spec.rb
|
377
|
-
- spec/mailcannon/librato_spec.rb
|
378
357
|
- spec/mailcannon/stamp_spec.rb
|
379
358
|
- spec/mailcannon/workers/barrel_spec.rb
|
359
|
+
- spec/mailcannon/workers/envelope_bag_reduce_job_spec.rb
|
380
360
|
- spec/mailcannon_spec.rb
|
381
361
|
- spec/spec_helper.rb
|
382
362
|
- spec/support/mongoid.yml
|
data/lib/mailcannon/airbrake.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module MailCannon::Airbrake
|
2
|
-
extend self
|
3
|
-
|
4
|
-
def available?
|
5
|
-
if ENV['AIRBRAKE_TOKEN']
|
6
|
-
true
|
7
|
-
else
|
8
|
-
false
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def authenticate
|
13
|
-
Airbrake.configure do |config|
|
14
|
-
config.api_key = ENV['AIRBRAKE_TOKEN']
|
15
|
-
config.host = 'api.airbrake.io'
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/mailcannon/librato.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
module MailCannon::Librato
|
2
|
-
extend self
|
3
|
-
|
4
|
-
def available?
|
5
|
-
if ENV['LIBRATO_USER'] && ENV['LIBRATO_TOKEN']
|
6
|
-
true
|
7
|
-
else
|
8
|
-
false
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def authenticate
|
13
|
-
Librato::Metrics.authenticate(ENV['LIBRATO_USER'], ENV['LIBRATO_TOKEN']) if available?
|
14
|
-
end
|
15
|
-
end
|