mailgun-tracking 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9dca3bb2ecebb3b77835850e80c913df78b24c03
4
+ data.tar.gz: 3b468bcfc59e17b6bbad6d92dbc5f7b57d7d2be9
5
+ SHA512:
6
+ metadata.gz: eb8b9b8227712aea6e298118aa98a39d9b82590b611056aa56f914072183b55c98cf53e2f14ceec8215247a690bed332f0a2fbd2eda670cf462c13b75098fd6e
7
+ data.tar.gz: 10652711a22a8d7c8fcee8404cc3dc4524e189cf0ea208211d7e43429bda35d831fb62a58349364d47d8afa8d97ce6c5506a012cb780cd1b0eeddd6fff9fb666
@@ -0,0 +1,45 @@
1
+ require 'mailgun/tracking/exceptions'
2
+ require 'mailgun/tracking/listener'
3
+ require 'mailgun/tracking/rack'
4
+ require 'mailgun/tracking/signature'
5
+ require 'mailgun/tracking/version'
6
+
7
+ module Mailgun
8
+ module Tracking
9
+ DEFAULT_ENDPOINT = '/mailgun'.freeze
10
+
11
+ class << self
12
+ attr_accessor :api_key,
13
+ :endpoint
14
+
15
+ def configure
16
+ yield(self)
17
+ end
18
+ end
19
+
20
+ self.endpoint = DEFAULT_ENDPOINT
21
+
22
+ module_function
23
+
24
+ def subscribe(event, &block)
25
+ listener.add_subscriber(event, &block)
26
+ end
27
+
28
+ def broadcast(event, payload)
29
+ Signature.verify!(payload)
30
+ listener.broadcast(event, payload)
31
+ end
32
+
33
+ def clear_cached_variables
34
+ @listener = nil
35
+ end
36
+
37
+ private
38
+
39
+ module_function
40
+
41
+ def listener
42
+ @listener ||= Listener.new
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ module Mailgun
2
+ module Tracking
3
+ Error = Class.new(StandardError)
4
+ InvalidSignature = Class.new(Error)
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ module Mailgun
2
+ module Tracking
3
+ class Listener
4
+ attr_reader :subscribers
5
+
6
+ def initialize
7
+ @subscribers = Hash.new { |h, k| h[k] = [] }
8
+ end
9
+
10
+ def add_subscriber(event, &block)
11
+ @subscribers[event.to_sym] << block
12
+ end
13
+
14
+ def broadcast(event, payload)
15
+ @subscribers[event.to_sym].each { |subscriber| subscriber.call(payload) }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ module Mailgun
2
+ module Tracking
3
+ class Rack
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ @env = env
10
+
11
+ if mailgun_tracking_request?
12
+ Mailgun::Tracking.broadcast(request.params.fetch('event'), request.params)
13
+ [200, {}, []]
14
+ else
15
+ @app.call(env)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def request
22
+ ::Rack::Request.new(@env)
23
+ end
24
+
25
+ def mailgun_tracking_request?
26
+ return false unless request.post?
27
+ return false unless request.path == Mailgun::Tracking.endpoint
28
+ true
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ require 'openssl'
2
+
3
+ module Mailgun
4
+ module Tracking
5
+ class Signature
6
+ def self.verify!(payload)
7
+ signature = new(payload)
8
+ raise InvalidSignature unless signature.valid?
9
+ true
10
+ end
11
+
12
+ def initialize(payload)
13
+ @token = payload.fetch('token')
14
+ @timestamp = payload.fetch('timestamp')
15
+ @signature = payload.fetch('signature')
16
+ end
17
+
18
+ def valid?
19
+ @signature == OpenSSL::HMAC.hexdigest(digest, Mailgun::Tracking.api_key, data)
20
+ end
21
+
22
+ private
23
+
24
+ def digest
25
+ OpenSSL::Digest::SHA256.new
26
+ end
27
+
28
+ def data
29
+ [@timestamp, @token].join
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module Mailgun
2
+ module Tracking
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ {
2
+ "domain": "mail.example.com",
3
+ "Message-Id": "<20160329071939.35138.9413.6915422C@mail.example.com>",
4
+ "event": "delivered",
5
+ "timestamp": "1499697910",
6
+ "token": "b5751a49a024483da8d41c3684f98b8f",
7
+ "signature": "374e0b1a3deeb57318c783d43ff71093fbf26406a452761dab91bf346a93b49e"
8
+ }
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Mailgun::Tracking::Listener do
4
+ subject(:listener) { described_class.new }
5
+
6
+ let(:block) { proc {} }
7
+
8
+ describe '#add_subscriber' do
9
+ it 'adds subscriber' do
10
+ expect { listener.add_subscriber(:delivered, &block) }.to change(listener, :subscribers)
11
+ .from({})
12
+ .to(delivered: [block])
13
+ end
14
+
15
+ it 'adds multiple subscribers with the same name' do
16
+ expect do
17
+ listener.add_subscriber(:delivered, &block)
18
+ listener.add_subscriber('delivered', &block)
19
+ end.to change(listener, :subscribers).from({}).to(delivered: [block, block])
20
+ end
21
+ end
22
+
23
+ describe '#broadcast' do
24
+ let(:payload) { fixture('delivered.json') }
25
+
26
+ before do
27
+ allow(block).to receive(:call)
28
+ listener.add_subscriber(:delivered, &block)
29
+ end
30
+
31
+ it 'executes block' do
32
+ listener.broadcast(:delivered, payload)
33
+ expect(block).to have_received(:call).with(payload)
34
+ end
35
+
36
+ it 'executes multiple blocks' do
37
+ listener.add_subscriber(:delivered, &block)
38
+ listener.broadcast('delivered', payload)
39
+ expect(block).to have_received(:call).with(payload).twice
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Mailgun::Tracking::Rack do
4
+ subject(:rack) { described_class.new(app) }
5
+
6
+ describe '#call' do
7
+ let(:app) { proc { [200, {}, []] } }
8
+ let(:payload) { fixture('delivered.json') }
9
+ let(:code) { rack.call(env)[0] }
10
+
11
+ before { allow(Mailgun::Tracking).to receive(:broadcast) }
12
+
13
+ context 'when a request to a endpoint with a POST method' do
14
+ let(:env) { env_for('http://localhost:3000/mailgun', method: :post, params: payload) }
15
+
16
+ it { expect(code).to eq(200) }
17
+ it do
18
+ code
19
+ expect(Mailgun::Tracking).to have_received(:broadcast).with('delivered', payload)
20
+ end
21
+ end
22
+
23
+ context 'when a request to a endpoint with a POST method' do
24
+ let(:env) { env_for('http://localhost:3000/mailgun', method: :get, params: payload) }
25
+
26
+ it { expect(code).to eq(200) }
27
+ it do
28
+ code
29
+ expect(Mailgun::Tracking).not_to have_received(:broadcast)
30
+ end
31
+ end
32
+
33
+ context 'when the request is not to the endpoint' do
34
+ let(:env) { env_for('http://localhost:3000') }
35
+ let(:code) { rack.call(env)[0] }
36
+
37
+ it { expect(code).to eq(200) }
38
+ it do
39
+ code
40
+ expect(Mailgun::Tracking).not_to have_received(:broadcast)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Mailgun::Tracking::Signature do
4
+ subject(:signature) { described_class.new(payload) }
5
+
6
+ let(:payload) { fixture('delivered.json') }
7
+
8
+ describe '.verify!' do
9
+ context 'when the signature comparison is successful' do
10
+ it { expect(described_class.verify!(payload)).to be true }
11
+ end
12
+
13
+ context 'when the signature comparison is unsuccessful' do
14
+ before { payload.merge!('timestamp' => '') }
15
+
16
+ it { expect { described_class.verify!(payload) }.to raise_error(Mailgun::Tracking::InvalidSignature) }
17
+ end
18
+ end
19
+
20
+ describe '#valid?' do
21
+ context 'when the signature comparison is successful' do
22
+ it { is_expected.to be_valid }
23
+ end
24
+
25
+ context 'when the signature comparison is unsuccessful' do
26
+ before { payload.merge!('timestamp' => '') }
27
+
28
+ it { is_expected.not_to be_valid }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Mailgun::Tracking do
4
+ describe '.configure' do
5
+ it 'setup block yields self' do
6
+ described_class.configure do |config|
7
+ expect(described_class).to eq(config)
8
+ end
9
+ end
10
+ end
11
+
12
+ describe '.subscribe' do
13
+ let(:listener) { instance_double(Mailgun::Tracking::Listener) }
14
+
15
+ before do
16
+ described_class.clear_cached_variables
17
+ allow(listener).to receive(:add_subscriber)
18
+ allow(Mailgun::Tracking::Listener).to receive(:new) { listener }
19
+ end
20
+
21
+ it 'subscribes on event' do
22
+ described_class.subscribe(:delivered) {}
23
+
24
+ expect(listener).to have_received(:add_subscriber)
25
+ .with(:delivered)
26
+ end
27
+ end
28
+
29
+ describe '.broadcast' do
30
+ let(:payload) { fixture('delivered.json') }
31
+ let(:listener) { instance_double(Mailgun::Tracking::Listener) }
32
+
33
+ before do
34
+ described_class.clear_cached_variables
35
+ allow(Mailgun::Tracking::Signature).to receive(:verify!)
36
+ allow(listener).to receive(:broadcast)
37
+ allow(Mailgun::Tracking::Listener).to receive(:new) { listener }
38
+ described_class.broadcast(:delivered, payload)
39
+ end
40
+
41
+ it 'verify signature' do
42
+ expect(Mailgun::Tracking::Signature).to have_received(:verify!).with(payload)
43
+ end
44
+
45
+ it 'broadcasts an event' do
46
+ expect(listener).to have_received(:broadcast).with(:delivered, payload)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ require 'rack'
2
+ require 'simplecov'
3
+ require 'bundler/setup'
4
+ require 'mailgun/tracking'
5
+
6
+ require_relative 'support/fixture'
7
+ require_relative 'support/rack_helpers'
8
+
9
+ SimpleCov.start
10
+
11
+ Mailgun::Tracking.configure do |config|
12
+ config.api_key = 'key-qblubkqnkdn4lfes5oscf57ryllaia42'
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ config.include(RackHelpers)
17
+
18
+ config.expect_with :rspec do |c|
19
+ c.syntax = :expect
20
+ end
21
+
22
+ config.order = :random
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ class FixtureFinder
4
+ FIXTURE_PATH = File.expand_path('../fixtures', __dir__)
5
+
6
+ def initialize(file)
7
+ @file = file
8
+ end
9
+
10
+ def find
11
+ File.read(FIXTURE_PATH + '/' + @file)
12
+ end
13
+ end
14
+
15
+ def fixture(file)
16
+ JSON.parse(FixtureFinder.new(file).find)
17
+ end
@@ -0,0 +1,5 @@
1
+ module RackHelpers
2
+ def env_for(url, options = {})
3
+ Rack::MockRequest.env_for(url, options)
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mailgun-tracking
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Artem Chubchenko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-07-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Integration for Mailgun Webhooks
14
+ email:
15
+ - artem.chubchenko@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/mailgun/tracking.rb
21
+ - lib/mailgun/tracking/exceptions.rb
22
+ - lib/mailgun/tracking/listener.rb
23
+ - lib/mailgun/tracking/rack.rb
24
+ - lib/mailgun/tracking/signature.rb
25
+ - lib/mailgun/tracking/version.rb
26
+ - spec/fixtures/delivered.json
27
+ - spec/mailgun/tracking/listener_spec.rb
28
+ - spec/mailgun/tracking/rack_spec.rb
29
+ - spec/mailgun/tracking/signature_spec.rb
30
+ - spec/mailgun/tracking_spec.rb
31
+ - spec/spec_helper.rb
32
+ - spec/support/fixture.rb
33
+ - spec/support/rack_helpers.rb
34
+ homepage: https://github.com/chubchenko/mailgun-tracking
35
+ licenses:
36
+ - MIT
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.0.0
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.5.1
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: Integration for Mailgun Webhooks
58
+ test_files:
59
+ - spec/fixtures/delivered.json
60
+ - spec/spec_helper.rb
61
+ - spec/mailgun/tracking_spec.rb
62
+ - spec/mailgun/tracking/listener_spec.rb
63
+ - spec/mailgun/tracking/rack_spec.rb
64
+ - spec/mailgun/tracking/signature_spec.rb
65
+ - spec/support/fixture.rb
66
+ - spec/support/rack_helpers.rb