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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/mailgun/tracking/templates/mailgun_tracking.rb.erb +1 -1
  3. data/lib/mailgun-tracking.rb +3 -0
  4. data/lib/mailgun/tracking.rb +65 -33
  5. data/lib/mailgun/tracking/auth.rb +52 -0
  6. data/lib/mailgun/tracking/configuration.rb +5 -9
  7. data/lib/mailgun/tracking/fanout.rb +18 -0
  8. data/lib/mailgun/tracking/middleware.rb +10 -47
  9. data/lib/mailgun/tracking/railtie.rb +2 -2
  10. data/lib/mailgun/tracking/version.rb +8 -7
  11. data/spec/dummy/logs/test.log +77 -0
  12. data/spec/dummy/rails/logs/test.log +19 -11
  13. data/spec/mailgun/tracking/auth_spec.rb +31 -0
  14. data/spec/mailgun/tracking/middleware_spec.rb +43 -51
  15. data/spec/mailgun/tracking_spec.rb +11 -4
  16. data/spec/support/shared_examples/integration/acts_as_rack.rb +6 -4
  17. metadata +18 -53
  18. data/lib/mailgun/tracking/exceptions.rb +0 -11
  19. data/lib/mailgun/tracking/listener.rb +0 -55
  20. data/lib/mailgun/tracking/notifier.rb +0 -61
  21. data/lib/mailgun/tracking/payload.rb +0 -96
  22. data/lib/mailgun/tracking/request.rb +0 -47
  23. data/lib/mailgun/tracking/signature.rb +0 -48
  24. data/lib/mailgun/tracking/subscriber.rb +0 -26
  25. data/lib/mailgun/tracking/subscriber/all_messages.rb +0 -37
  26. data/lib/mailgun/tracking/subscriber/evented.rb +0 -40
  27. data/lib/mailgun/tracking/util.rb +0 -67
  28. data/spec/mailgun/tracking/listener_spec.rb +0 -48
  29. data/spec/mailgun/tracking/notifier_spec.rb +0 -66
  30. data/spec/mailgun/tracking/payload_spec.rb +0 -73
  31. data/spec/mailgun/tracking/request_spec.rb +0 -63
  32. data/spec/mailgun/tracking/signature_spec.rb +0 -65
  33. data/spec/mailgun/tracking/subscriber/all_messages_spec.rb +0 -13
  34. data/spec/mailgun/tracking/subscriber/evented_spec.rb +0 -14
  35. data/spec/mailgun/tracking/subscriber_spec.rb +0 -15
  36. data/spec/mailgun/tracking/util_spec.rb +0 -155
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mailgun
4
- module Tracking
5
- # Wraps the {Listener} which gives a friendlier way to subscribe or broadcast.
6
- class Notifier
7
- # Initializes a new Notifier object.
8
- #
9
- # @param listener [Mailgun::Tracking::Listener] Event listener.
10
- #
11
- # @return [Mailgun::Tracking::Notifier]
12
- def initialize(listener = Listener.new)
13
- @listener = listener
14
- end
15
-
16
- # Returns true if there is at least one subscriber.
17
- #
18
- # @return [Boolean]
19
- def empty?
20
- listener.subscribers.empty?
21
- end
22
-
23
- # Adds subscriber for the specified event.
24
- #
25
- # @param event [Symbol, String] The name of event.
26
- # @param callable [Proc, Class] The listener of event.
27
- # The callable objects should respond to call.
28
- #
29
- # @return [NilClass]
30
- def subscribe(event, callable = Proc.new)
31
- listener.add_subscriber(event, callable)
32
- end
33
-
34
- # Adds a subscriber for all events.
35
- #
36
- # @param callable [Proc, Class] The listener of event.
37
- # The callable objects should respond to call.
38
- #
39
- # @return [NilClass]
40
- def all(callable = Proc.new)
41
- listener.add_subscriber(nil, callable)
42
- end
43
-
44
- # Broadcasts parameters to event subscribers.
45
- #
46
- # @param event [String] The name of event.
47
- # @param payload [Hash] The response parameters.
48
- #
49
- # @return [NilClass]
50
- def broadcast(event, payload)
51
- Signature.verify!(payload)
52
- listener.broadcast(event, payload)
53
- end
54
-
55
- private
56
-
57
- # @return [Mailgun::Tracking::Listener]
58
- attr_reader :listener
59
- end
60
- end
61
- end
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'set'
4
-
5
- module Mailgun
6
- module Tracking
7
- # Payload object.
8
- class Payload
9
- TRY_TO_HASH = lambda do |value|
10
- return if value.nil?
11
-
12
- value.respond_to?(:to_hash) ? value.to_hash : value
13
- end.freeze
14
-
15
- # Initializes a new Payload object.
16
- #
17
- # @param values [Hash]
18
- #
19
- # @return [Mailgun::Tracking::Payload]
20
- def initialize(values = {})
21
- @values = Util.normalize(values)
22
-
23
- @values.each do |key, value|
24
- define_instance_methods([key], values) unless __metaclass__.method_defined?(key)
25
- @values[key] = Util.convert_to_payload_object(value)
26
- end
27
- end
28
-
29
- # Returns a value of the payload with the given key.
30
- #
31
- # @param key [String]
32
- #
33
- # @return a value of the payload.
34
- def [](key)
35
- @values[key]
36
- end
37
-
38
- # @return [Boolean]
39
- def ==(other)
40
- return false unless other.is_a?(Payload)
41
-
42
- values == other.values
43
- end
44
-
45
- alias eql? ==
46
-
47
- # @return [Integer] The object's hash value (for equality checking)
48
- def hash
49
- values.hash
50
- end
51
-
52
- # @return [Hash] Recursively convert payload objects to the hash.
53
- def to_hash
54
- @values.each_with_object({}) do |(key, value), memo|
55
- memo[key] =
56
- case value
57
- when Array
58
- value.map(&TRY_TO_HASH)
59
- else
60
- TRY_TO_HASH.call(value)
61
- end
62
- end
63
- end
64
-
65
- # @return [String] The string representation of the payload.
66
- def to_s
67
- JSON.pretty_generate(to_hash)
68
- end
69
-
70
- private
71
-
72
- # @return [Class]
73
- def __metaclass__
74
- class << self
75
- self
76
- end
77
- end
78
-
79
- # @return [void]
80
- def define_instance_methods(keys, values)
81
- __metaclass__.instance_eval do
82
- keys.each do |key|
83
- define_method(key) { @values[key] }
84
- next unless [FalseClass, TrueClass].include?(values[key].class)
85
-
86
- define_method(:"#{key}?") { @values[key] }
87
- end
88
- end
89
- end
90
-
91
- protected
92
-
93
- attr_reader :values
94
- end
95
- end
96
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rack/request'
4
-
5
- module Mailgun
6
- module Tracking
7
- # Provides a convenient interface to a Rack environment.
8
- class Request < ::Rack::Request
9
- # Checks if the request is respond to the specified URL.
10
- #
11
- # @return [Boolean]
12
- def mailgun_tracking?
13
- return false unless post?
14
- return false unless media_type == APPLICATION_JSON
15
-
16
- path == Configuration.instance.endpoint
17
- end
18
-
19
- # @return [Mailgun::Tracking::Payload]
20
- def payload
21
- @payload ||= Payload.new(params)
22
- end
23
-
24
- # A Mailgun::Tracking::Request::JSON is used to parsing JSON requests.
25
- module JSON
26
- APPLICATION_JSON = 'application/json'
27
- FORM_HASH = 'rack.request.form_hash'
28
- FORM_INPUT = 'rack.request.form_input'
29
-
30
- # Returns the data received in the request body.
31
- #
32
- # @return [Hash]
33
- def POST
34
- return super unless media_type == APPLICATION_JSON
35
-
36
- body = self.body.read
37
- self.body.rewind
38
- env.update(FORM_HASH => ::JSON.parse(body), FORM_INPUT => body)
39
-
40
- env[FORM_HASH]
41
- end
42
- end
43
-
44
- include JSON
45
- end
46
- end
47
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'openssl'
4
-
5
- module Mailgun
6
- module Tracking
7
- # A Mailgun::Tracking::Signature object is used to verify the signature.
8
- class Signature
9
- # Verify the signature of the response parameters.
10
- #
11
- # @param payload [Mailgun::Tracking::Payload]
12
- # @raise [InvalidSignature] Error raised when signature is invalid.
13
- #
14
- # @return [Boolean]
15
- # Always returns true.
16
- def self.verify!(payload)
17
- signature = new(payload)
18
- raise InvalidSignature unless signature.valid?
19
-
20
- true
21
- end
22
-
23
- # Initializes a new Signature object.
24
- #
25
- # @param payload [Mailgun::Tracking::Payload]
26
- #
27
- # @return [Mailgun::Tracking::Signature]
28
- def initialize(payload)
29
- @payload = payload
30
- end
31
-
32
- # @return [Boolean]
33
- def valid?
34
- @payload.signature.signature == \
35
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, Configuration.instance.api_key, data)
36
- end
37
-
38
- private
39
-
40
- # Joins the timestamp and the response token.
41
- #
42
- # @return [String]
43
- def data
44
- [@payload.signature.timestamp, @payload.signature.token].join
45
- end
46
- end
47
- end
48
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'subscriber/all_messages'
4
- require_relative 'subscriber/evented'
5
-
6
- module Mailgun
7
- module Tracking
8
- # Namespace for classes that wraps subscribers.
9
- module Subscriber
10
- # Determines the type of subscription.
11
- #
12
- # @param name [Symbol, String] The name of event.
13
- # @param callable [Proc, Class] The callable object.
14
- # The callable objects should respond to call.
15
- #
16
- # @return [Mailgun::Tracking::Subscriber::Evented, Mailgun::Tracking::Subscriber::AllMessages]
17
- def self.for(name, callable)
18
- if name
19
- Evented.new(name, callable)
20
- else
21
- AllMessages.new(callable)
22
- end
23
- end
24
- end
25
- end
26
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mailgun
4
- module Tracking
5
- module Subscriber
6
- # Wraps the subscriber for events.
7
- class AllMessages
8
- # Initializes a new AllMessages object.
9
- #
10
- # @param callable [Proc, Class] The callable object.
11
- # The callable objects should respond to call.
12
- #
13
- # @return [Mailgun::Tracking::Subscriber::AllMessages]
14
- def initialize(callable)
15
- @callable = callable
16
- end
17
-
18
- # Invokes the callable object.
19
- #
20
- # @param payload [Hash] The response parameters.
21
- #
22
- # @return [void]
23
- def call(payload)
24
- @callable.call(payload)
25
- end
26
-
27
- # Checks if a callable object is a subscribed to the specified event.
28
- #
29
- # @return [Boolean]
30
- # Always returns true.
31
- def subscribed_to?(*)
32
- true
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mailgun
4
- module Tracking
5
- module Subscriber
6
- # Wraps the evented subscriber.
7
- class Evented
8
- # Initializes a new Evented object.
9
- #
10
- # @param name [Symbol, String] The name of event.
11
- # @param callable [Proc, Class] The callable object.
12
- # The callable objects should respond to call.
13
- #
14
- # @return [Mailgun::Tracking::Subscriber::Evented]
15
- def initialize(name, callable)
16
- @name = name.to_sym
17
- @callable = callable
18
- end
19
-
20
- # Invokes the callable object.
21
- #
22
- # @param payload [Hash] The response parameters.
23
- #
24
- # @return [void]
25
- def call(payload)
26
- @callable.call(payload)
27
- end
28
-
29
- # Checks if a callable object is a subscribed to the specified event.
30
- #
31
- # @param name [String] The name of event.
32
- #
33
- # @return [Boolean]
34
- def subscribed_to?(name)
35
- @name == name.to_sym
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mailgun
4
- module Tracking
5
- # Utility methods.
6
- module Util
7
- class << self
8
- # Converts a hash of fields or an array of hashes into a {Payload}.
9
- #
10
- # @param data [Hash, Array] Hash of fields and values to be converted into a Payload.
11
- #
12
- # @return [Mailgun::Tracking::Payload, Array(Mailgun::Tracking::Payload)]
13
- def convert_to_payload_object(data)
14
- case data
15
- when Array
16
- data.map { |item| convert_to_payload_object(item) }
17
- when Hash
18
- Payload.new(data)
19
- else
20
- data
21
- end
22
- end
23
- end
24
-
25
- class << self
26
- # Returns a new hash with all keys converted to symbols in downcase.
27
- #
28
- # @param options [Hash]
29
- #
30
- # @return [Hash]
31
- def normalize(options = {})
32
- object = normalize_keys(options.dup)
33
- object
34
- end
35
-
36
- private
37
-
38
- def normalize_keys(options)
39
- return from_h(options) if options.is_a?(Hash)
40
- return from_ary(options) if options.is_a?(Array)
41
-
42
- options
43
- end
44
-
45
- def from_h(options)
46
- Hash[options.map do |key, value|
47
- [underscore(key), normalize_keys(value)]
48
- end]
49
- end
50
-
51
- def from_ary(options)
52
- options.map(&method(:normalize_keys))
53
- end
54
-
55
- def underscore(key)
56
- return key unless key.respond_to?(:to_sym)
57
-
58
- key.to_s
59
- .dup
60
- .tr('-', '_')
61
- .downcase
62
- .to_sym
63
- end
64
- end
65
- end
66
- end
67
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Mailgun::Tracking::Listener do
4
- subject(:listener) { described_class.new }
5
-
6
- let(:callable) { proc {} }
7
- let(:subscriber) { instance_double(Mailgun::Tracking::Subscriber::Evented) }
8
-
9
- describe '#add_subscriber' do
10
- before { allow(Mailgun::Tracking::Subscriber).to receive(:for).with(/delivered/, callable) { subscriber } }
11
-
12
- it 'adds subscriber' do
13
- expect { listener.add_subscriber(:delivered, callable) }.to change(listener, :subscribers)
14
- .from([])
15
- .to([subscriber])
16
- end
17
-
18
- it 'adds multiple subscribers with the same name' do
19
- expect do
20
- listener.add_subscriber(:delivered, callable)
21
- listener.add_subscriber('delivered', callable)
22
- end.to change(listener, :subscribers).from([]).to([subscriber, subscriber])
23
- end
24
- end
25
-
26
- describe '#broadcast' do
27
- let(:payload) { fixture('delivered.json') }
28
-
29
- before do
30
- allow(subscriber).to receive(:call)
31
- allow(subscriber).to receive(:subscribed_to?).with(/delivered/).and_return(true)
32
- allow(Mailgun::Tracking::Subscriber).to receive(:for).with(:delivered, callable) { subscriber }
33
-
34
- listener.add_subscriber(:delivered, callable)
35
- end
36
-
37
- it 'executes subscriber' do
38
- listener.broadcast(:delivered, payload)
39
- expect(subscriber).to have_received(:call).with(payload)
40
- end
41
-
42
- it 'executes multiple subscribers' do
43
- listener.add_subscriber(:delivered, callable)
44
- listener.broadcast('delivered', payload)
45
- expect(subscriber).to have_received(:call).with(payload).twice
46
- end
47
- end
48
- end