mailgun-tracking 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/mailgun/tracking/templates/mailgun_tracking.rb.erb +1 -1
- data/lib/mailgun-tracking.rb +3 -0
- data/lib/mailgun/tracking.rb +65 -33
- data/lib/mailgun/tracking/auth.rb +52 -0
- data/lib/mailgun/tracking/configuration.rb +5 -9
- data/lib/mailgun/tracking/fanout.rb +18 -0
- data/lib/mailgun/tracking/middleware.rb +10 -47
- data/lib/mailgun/tracking/railtie.rb +2 -2
- data/lib/mailgun/tracking/version.rb +8 -7
- data/spec/dummy/logs/test.log +77 -0
- data/spec/dummy/rails/logs/test.log +19 -11
- data/spec/mailgun/tracking/auth_spec.rb +31 -0
- data/spec/mailgun/tracking/middleware_spec.rb +43 -51
- data/spec/mailgun/tracking_spec.rb +11 -4
- data/spec/support/shared_examples/integration/acts_as_rack.rb +6 -4
- metadata +18 -53
- data/lib/mailgun/tracking/exceptions.rb +0 -11
- data/lib/mailgun/tracking/listener.rb +0 -55
- data/lib/mailgun/tracking/notifier.rb +0 -61
- data/lib/mailgun/tracking/payload.rb +0 -96
- data/lib/mailgun/tracking/request.rb +0 -47
- data/lib/mailgun/tracking/signature.rb +0 -48
- data/lib/mailgun/tracking/subscriber.rb +0 -26
- data/lib/mailgun/tracking/subscriber/all_messages.rb +0 -37
- data/lib/mailgun/tracking/subscriber/evented.rb +0 -40
- data/lib/mailgun/tracking/util.rb +0 -67
- data/spec/mailgun/tracking/listener_spec.rb +0 -48
- data/spec/mailgun/tracking/notifier_spec.rb +0 -66
- data/spec/mailgun/tracking/payload_spec.rb +0 -73
- data/spec/mailgun/tracking/request_spec.rb +0 -63
- data/spec/mailgun/tracking/signature_spec.rb +0 -65
- data/spec/mailgun/tracking/subscriber/all_messages_spec.rb +0 -13
- data/spec/mailgun/tracking/subscriber/evented_spec.rb +0 -14
- data/spec/mailgun/tracking/subscriber_spec.rb +0 -15
- data/spec/mailgun/tracking/util_spec.rb +0 -155
@@ -1,66 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Notifier do
|
4
|
-
subject(:notifier) { described_class.new(listener) }
|
5
|
-
|
6
|
-
let(:listener) { instance_double(Mailgun::Tracking::Listener) }
|
7
|
-
|
8
|
-
describe '#empty?' do
|
9
|
-
context 'when there is at least one subscriber' do
|
10
|
-
let(:subscriber) { instance_double(Mailgun::Tracking::Subscriber::AllMessages) }
|
11
|
-
|
12
|
-
before { allow(listener).to receive(:subscribers).and_return([subscriber]) }
|
13
|
-
|
14
|
-
it { is_expected.not_to be_empty }
|
15
|
-
end
|
16
|
-
|
17
|
-
context 'when there are no subscribers' do
|
18
|
-
before { allow(listener).to receive(:subscribers).and_return([]) }
|
19
|
-
|
20
|
-
it { is_expected.to be_empty }
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe '#subscribe' do
|
25
|
-
let(:callable) { proc {} }
|
26
|
-
|
27
|
-
before { allow(listener).to receive(:add_subscriber) }
|
28
|
-
|
29
|
-
it 'subscribes on event' do
|
30
|
-
notifier.subscribe(:delivered, callable)
|
31
|
-
|
32
|
-
expect(listener).to have_received(:add_subscriber)
|
33
|
-
.with(:delivered, callable)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
describe '#all' do
|
38
|
-
let(:callable) { proc {} }
|
39
|
-
|
40
|
-
before { allow(listener).to receive(:add_subscriber) }
|
41
|
-
|
42
|
-
it 'subscribes on all events' do
|
43
|
-
notifier.all(callable)
|
44
|
-
|
45
|
-
expect(listener).to have_received(:add_subscriber).with(nil, callable)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
describe '#broadcast' do
|
50
|
-
let(:payload) { Mailgun::Tracking::Payload.new(fixture('delivered.json')) }
|
51
|
-
|
52
|
-
before do
|
53
|
-
allow(Mailgun::Tracking::Signature).to receive(:verify!)
|
54
|
-
allow(listener).to receive(:broadcast)
|
55
|
-
notifier.broadcast(:delivered, payload)
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'verify signature' do
|
59
|
-
expect(Mailgun::Tracking::Signature).to have_received(:verify!).with(payload)
|
60
|
-
end
|
61
|
-
|
62
|
-
it 'broadcasts an event' do
|
63
|
-
expect(listener).to have_received(:broadcast).with(:delivered, payload)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Payload do
|
4
|
-
subject(:payload) { described_class.new(values) }
|
5
|
-
|
6
|
-
describe '#==' do
|
7
|
-
let(:values) { { 'foo' => 'bar' } }
|
8
|
-
|
9
|
-
it { is_expected.to eq(described_class.new('foo' => 'bar')) }
|
10
|
-
it { is_expected.not_to eq(described_class.new('foo': 'rab')) }
|
11
|
-
it { is_expected.not_to eq('foo') }
|
12
|
-
end
|
13
|
-
|
14
|
-
describe '#respond_to?' do
|
15
|
-
let(:values) { { 'foo' => 'bar', 'boolean': true } }
|
16
|
-
|
17
|
-
it { is_expected.to respond_to(:foo) }
|
18
|
-
it { is_expected.to respond_to(:boolean?) }
|
19
|
-
it { is_expected.not_to respond_to(:foo?) }
|
20
|
-
end
|
21
|
-
|
22
|
-
describe '#[]' do
|
23
|
-
let(:values) { { 'Message-Id' => '<payload@mailgun-tracking.com>' } }
|
24
|
-
|
25
|
-
it 'returns the value object from values' do
|
26
|
-
expect(payload[:message_id]).to eq('<payload@mailgun-tracking.com>')
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
describe '#hash' do
|
31
|
-
let(:values) { { 'foo' => 'bar' } }
|
32
|
-
|
33
|
-
it { expect(payload.hash).to eq(described_class.new('foo' => 'bar').hash) }
|
34
|
-
it { expect(payload.hash).not_to eq(described_class.new('foo': 'rab').hash) }
|
35
|
-
it { expect(payload.hash).not_to eq('foo'.hash) }
|
36
|
-
end
|
37
|
-
|
38
|
-
describe '#to_hash' do
|
39
|
-
let(:values) do
|
40
|
-
{
|
41
|
-
'foo' => 'bar',
|
42
|
-
'list' => [described_class.new('foo' => 'bar')],
|
43
|
-
'bar' => { 'foo' => 'bar', 'bar' => described_class.new('foo' => 'bar') }
|
44
|
-
}
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'recursively call to_hash on its values' do
|
48
|
-
expect(payload.to_hash).to eql(
|
49
|
-
foo: 'bar',
|
50
|
-
list: [{ foo: 'bar' }],
|
51
|
-
bar: { foo: 'bar', bar: { foo: 'bar' } }
|
52
|
-
)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
describe '#to_s' do
|
57
|
-
let(:values) do
|
58
|
-
{
|
59
|
-
'foo' => 'bar',
|
60
|
-
'list' => [described_class.new('foo' => 'bar')],
|
61
|
-
'bar' => { 'foo' => 'bar', 'bar' => described_class.new('foo' => 'bar') }
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'will call to_s for all embedded stripe objects' do
|
66
|
-
expect(payload.to_s).to eql(JSON.pretty_generate(
|
67
|
-
foo: 'bar',
|
68
|
-
list: [{ foo: 'bar' }],
|
69
|
-
bar: { foo: 'bar', bar: { foo: 'bar' } }
|
70
|
-
))
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Request do
|
4
|
-
subject(:request) { described_class.new(env) }
|
5
|
-
|
6
|
-
describe '#mailgun_tracking?' do
|
7
|
-
context 'when a request to an endpoint without a POST method' do
|
8
|
-
let(:env) { env_for('http://localhost:3000/mailgun', method: :get) }
|
9
|
-
|
10
|
-
it { is_expected.not_to be_mailgun_tracking }
|
11
|
-
end
|
12
|
-
|
13
|
-
context 'when a request to an endpoint with not acceptable content type' do
|
14
|
-
let(:env) do
|
15
|
-
env_for('http://localhost:3000/mailgun', method: :post, 'CONTENT_TYPE' => 'application/x-www-form-urlencoded')
|
16
|
-
end
|
17
|
-
|
18
|
-
it { is_expected.not_to be_mailgun_tracking }
|
19
|
-
end
|
20
|
-
|
21
|
-
context 'when the request is not to the endpoint' do
|
22
|
-
let(:env) do
|
23
|
-
env_for('http://localhost:3000/_mailgun', method: :post, 'CONTENT_TYPE' => 'application/json; charset=utf-8')
|
24
|
-
end
|
25
|
-
|
26
|
-
it { is_expected.not_to be_mailgun_tracking }
|
27
|
-
end
|
28
|
-
|
29
|
-
context 'when all the above conditions are met' do
|
30
|
-
let(:env) do
|
31
|
-
env_for('http://localhost:3000/mailgun', method: :post, 'CONTENT_TYPE' => 'application/json; charset=utf-8')
|
32
|
-
end
|
33
|
-
|
34
|
-
it { is_expected.to be_mailgun_tracking }
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
describe '#payload' do
|
39
|
-
let(:env) { env_for('http://localhost:3000/mailgun', method: :post, params: { 'signature' => {} }) }
|
40
|
-
|
41
|
-
it 'returns payload' do
|
42
|
-
expect(request.payload).to be_an_instance_of(Mailgun::Tracking::Payload)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
context 'when application/json request' do
|
47
|
-
let(:env) { env_for('/', method: :post, input: input, 'CONTENT_TYPE' => content_type) }
|
48
|
-
let(:input) { 'foo=bar' }
|
49
|
-
let(:content_type) { 'application/json; charset=utf-8' }
|
50
|
-
|
51
|
-
it 'rewinds input' do
|
52
|
-
expect(request.body.read).to eq(input)
|
53
|
-
end
|
54
|
-
|
55
|
-
context 'when input is a hash' do
|
56
|
-
let(:input) { '{"qux": "bin"}' }
|
57
|
-
|
58
|
-
it 'adds a parsed hash to POST params' do
|
59
|
-
expect(request.params['qux']).to eq('bin')
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Signature do
|
4
|
-
subject(:signature) { described_class.new(payload) }
|
5
|
-
|
6
|
-
describe '.verify!' do
|
7
|
-
context 'when the signature comparison is successful' do
|
8
|
-
let(:payload) do
|
9
|
-
Mailgun::Tracking::Payload.new(
|
10
|
-
signature: {
|
11
|
-
timestamp: '1499697910',
|
12
|
-
token: 'b5751a49a024483da8d41c3684f98b8f',
|
13
|
-
signature: '374e0b1a3deeb57318c783d43ff71093fbf26406a452761dab91bf346a93b49e'
|
14
|
-
}
|
15
|
-
)
|
16
|
-
end
|
17
|
-
|
18
|
-
it { expect(described_class.verify!(payload)).to be true }
|
19
|
-
end
|
20
|
-
|
21
|
-
context 'when the signature comparison is unsuccessful' do
|
22
|
-
let(:payload) do
|
23
|
-
Mailgun::Tracking::Payload.new(
|
24
|
-
signature: {
|
25
|
-
timestamp: '',
|
26
|
-
token: 'b5751a49a024483da8d41c3684f98b8f',
|
27
|
-
signature: '374e0b1a3deeb57318c783d43ff71093fbf26406a452761dab91bf346a93b49e'
|
28
|
-
}
|
29
|
-
)
|
30
|
-
end
|
31
|
-
|
32
|
-
it { expect { described_class.verify!(payload) }.to raise_error(Mailgun::Tracking::InvalidSignature) }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe '#valid?' do
|
37
|
-
context 'when the signature comparison is successful' do
|
38
|
-
let(:payload) do
|
39
|
-
Mailgun::Tracking::Payload.new(
|
40
|
-
signature: {
|
41
|
-
timestamp: '1499697910',
|
42
|
-
token: 'b5751a49a024483da8d41c3684f98b8f',
|
43
|
-
signature: '374e0b1a3deeb57318c783d43ff71093fbf26406a452761dab91bf346a93b49e'
|
44
|
-
}
|
45
|
-
)
|
46
|
-
end
|
47
|
-
|
48
|
-
it { is_expected.to be_valid }
|
49
|
-
end
|
50
|
-
|
51
|
-
context 'when the signature comparison is unsuccessful' do
|
52
|
-
let(:payload) do
|
53
|
-
Mailgun::Tracking::Payload.new(
|
54
|
-
signature: {
|
55
|
-
timestamp: '',
|
56
|
-
token: 'b5751a49a024483da8d41c3684f98b8f',
|
57
|
-
signature: '374e0b1a3deeb57318c783d43ff71093fbf26406a452761dab91bf346a93b49e'
|
58
|
-
}
|
59
|
-
)
|
60
|
-
end
|
61
|
-
|
62
|
-
it { is_expected.not_to be_valid }
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Subscriber::AllMessages do
|
4
|
-
subject(:subscriber) { described_class.new(callable) }
|
5
|
-
|
6
|
-
it_behaves_like 'subscriber'
|
7
|
-
|
8
|
-
describe '#subscribed_to?' do
|
9
|
-
let(:callable) { proc {} }
|
10
|
-
|
11
|
-
it { is_expected.to be_subscribed_to(:any_event) }
|
12
|
-
end
|
13
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Subscriber::Evented do
|
4
|
-
subject(:subscriber) { described_class.new('delivered', callable) }
|
5
|
-
|
6
|
-
it_behaves_like 'subscriber'
|
7
|
-
|
8
|
-
describe '#subscribed_to?' do
|
9
|
-
let(:callable) { proc {} }
|
10
|
-
|
11
|
-
it { is_expected.to be_subscribed_to(:delivered) }
|
12
|
-
it { is_expected.not_to be_subscribed_to(:any_event) }
|
13
|
-
end
|
14
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Subscriber do
|
4
|
-
describe '.for' do
|
5
|
-
let(:callable) { proc {} }
|
6
|
-
|
7
|
-
it 'returns an instance of Mailgun::Tracking::Subscriber::AllMessages' do
|
8
|
-
expect(described_class.for(nil, callable)).to be_instance_of(Mailgun::Tracking::Subscriber::AllMessages)
|
9
|
-
end
|
10
|
-
|
11
|
-
it 'returns an instance of Mailgun::Tracking::Subscriber::Evented' do
|
12
|
-
expect(described_class.for('delivered', callable)).to be_instance_of(Mailgun::Tracking::Subscriber::Evented)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,155 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Util do
|
4
|
-
describe '.normalize' do
|
5
|
-
context 'when the keys of the object are strings' do
|
6
|
-
let(:object) do
|
7
|
-
{ 'a' => 1, 'b' => 2 }
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'returns normalized object' do
|
11
|
-
expect(described_class.normalize(object)).to eq(a: 1, b: 2)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
context 'when the object has deep string keys' do
|
16
|
-
let(:object) do
|
17
|
-
{ 'a' => { 'b' => { 'c' => 3 } } }
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'returns normalized object' do
|
21
|
-
expect(described_class.normalize(object)).to eq(a: { b: { c: 3 } })
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context 'when the keys of the object are symbols' do
|
26
|
-
let(:object) do
|
27
|
-
{ a: 1, b: 2 }
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'returns normalized object' do
|
31
|
-
expect(described_class.normalize(object)).to eq(object)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context 'when an object has deep symbol keys' do
|
36
|
-
let(:object) do
|
37
|
-
{ a: { b: { c: 3 } } }
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'returns normalized object' do
|
41
|
-
expect(described_class.normalize(object)).to eq(object)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
context 'when the keys of the object are mixed' do
|
46
|
-
let(:object) do
|
47
|
-
{ :a => 1, 'b' => 2 }
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'returns normalized object' do
|
51
|
-
expect(described_class.normalize(object)).to eq(a: 1, b: 2)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
context 'when the keys of the object are integers' do
|
56
|
-
let(:object) do
|
57
|
-
{ 0 => 1, 1 => 2 }
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'returns normalized object' do
|
61
|
-
expect(described_class.normalize(object)).to eq(0 => 1, 1 => 2)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
context 'when an object has deep integer keys' do
|
66
|
-
let(:object) do
|
67
|
-
{ 0 => { 1 => { 2 => 3 } } }
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'returns normalized object' do
|
71
|
-
expect(described_class.normalize(object)).to eq(0 => { 1 => { 2 => 3 } })
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
context 'when the key of the object is illegal symbol' do
|
76
|
-
let(:object) do
|
77
|
-
{ [] => 3 }
|
78
|
-
end
|
79
|
-
|
80
|
-
it 'returns normalized object' do
|
81
|
-
expect(described_class.normalize(object)).to eq([] => 3)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
context 'when an object has deep illegal symbol keys' do
|
86
|
-
let(:object) do
|
87
|
-
{ [] => { [] => 3 } }
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'returns normalized object' do
|
91
|
-
expect(described_class.normalize(object)).to eq([] => { [] => 3 })
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
context 'when the keys of the object are upcase strings' do
|
96
|
-
let(:object) do
|
97
|
-
{ 'A' => 1, 'B' => 2 }
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'returns normalized object' do
|
101
|
-
expect(described_class.normalize(object)).to eq(a: 1, b: 2)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
context 'when an object has deep upcase string keys' do
|
106
|
-
let(:object) do
|
107
|
-
{ 'A' => { 'B' => { 'C' => 3 } } }
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'returns normalized object' do
|
111
|
-
expect(described_class.normalize(object)).to eq(a: { b: { c: 3 } })
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
context 'when the object value is an array of stringified hashes' do
|
116
|
-
let(:object) do
|
117
|
-
{ 'a' => [{ 'b' => 2 }, { 'c' => 3 }, 4] }
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'returns normalized object' do
|
121
|
-
expect(described_class.normalize(object)).to eq(a: [{ b: 2 }, { c: 3 }, 4])
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
context 'when the object value is an array of symbolized hashes' do
|
126
|
-
let(:object) do
|
127
|
-
{ a: [{ b: 2 }, { c: 3 }, 4] }
|
128
|
-
end
|
129
|
-
|
130
|
-
it 'returns normalized object' do
|
131
|
-
expect(described_class.normalize(object)).to eq(object)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
context 'when the object value is an array of mixed hashes' do
|
136
|
-
let(:object) do
|
137
|
-
{ a: [{ b: 2 }, { 'c' => 3 }, 4] }
|
138
|
-
end
|
139
|
-
|
140
|
-
it 'returns normalized object' do
|
141
|
-
expect(described_class.normalize(object)).to eq(a: [{ b: 2 }, { c: 3 }, 4])
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
context 'when the object value is an array of upcased hashes' do
|
146
|
-
let(:object) do
|
147
|
-
{ 'A' => [{ 'B' => 2 }, { 'C' => 3 }, 4] }
|
148
|
-
end
|
149
|
-
|
150
|
-
it 'returns normalized object' do
|
151
|
-
expect(described_class.normalize(object)).to eq(a: [{ b: 2 }, { c: 3 }, 4])
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|