ruby-push-notifications 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/.travis.yml CHANGED
@@ -5,3 +5,10 @@ rvm:
5
5
  - "2.0.0"
6
6
  - "1.9.3"
7
7
  env: CODECLIMATE_REPO_TOKEN=efdb12c380287a25b2b26362aa710a9b59020122cbe4edecf0d353bf50e0046a
8
+ notifications:
9
+ webhooks:
10
+ urls:
11
+ - https://webhooks.gitter.im/e/b740ebfeb7e0dba10293
12
+ on_success: change # options: [always|never|change] default: always
13
+ on_failure: always # options: [always|never|change] default: always
14
+ on_start: false # default: false
data/Gemfile.lock CHANGED
@@ -2,6 +2,7 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  ruby-push-notifications (0.1.0)
5
+ builder (~> 3.2)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
@@ -13,6 +14,7 @@ GEM
13
14
  thread_safe (~> 0.3, >= 0.3.4)
14
15
  tzinfo (~> 1.1)
15
16
  addressable (2.3.8)
17
+ builder (3.2.2)
16
18
  codeclimate-test-reporter (0.4.7)
17
19
  simplecov (>= 0.7.1, < 1.0.0)
18
20
  crack (0.4.2)
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Ruby Push Notifications
2
2
 
3
- [![Build Status](https://travis-ci.org/calonso/ruby-push-notifications.svg)](https://travis-ci.org/calonso/ruby-push-notifications) [![Dependency Status](https://gemnasium.com/calonso/ruby-push-notifications.svg)](https://gemnasium.com/calonso/ruby-push-notifications) [![Code Climate](https://codeclimate.com/github/calonso/ruby-push-notifications/badges/gpa.svg)](https://codeclimate.com/github/calonso/ruby-push-notifications) [![Test Coverage](https://codeclimate.com/github/calonso/ruby-push-notifications/badges/coverage.svg)](https://codeclimate.com/github/calonso/ruby-push-notifications) [![Gem Version](https://badge.fury.io/rb/ruby-push-notifications.svg)](http://badge.fury.io/rb/ruby-push-notifications)
3
+ [![Build Status](https://travis-ci.org/calonso/ruby-push-notifications.svg)](https://travis-ci.org/calonso/ruby-push-notifications) [![Dependency Status](https://gemnasium.com/calonso/ruby-push-notifications.svg)](https://gemnasium.com/calonso/ruby-push-notifications) [![Code Climate](https://codeclimate.com/github/calonso/ruby-push-notifications/badges/gpa.svg)](https://codeclimate.com/github/calonso/ruby-push-notifications) [![Test Coverage](https://codeclimate.com/github/calonso/ruby-push-notifications/badges/coverage.svg)](https://codeclimate.com/github/calonso/ruby-push-notifications) [![Gem Version](https://badge.fury.io/rb/ruby-push-notifications.svg)](http://badge.fury.io/rb/ruby-push-notifications) [![Join the chat at https://gitter.im/calonso/ruby-push-notifications](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/calonso/ruby-push-notifications?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
4
 
5
- ###iOS and Android Push Notifications made easy!
5
+ ###iOS, Android and Windows Phone Push Notifications made easy!
6
6
 
7
7
  ## Features
8
8
 
@@ -38,6 +38,7 @@ For completely detailed examples:
38
38
 
39
39
  1. [Apple iOS example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/apns.rb)
40
40
  2. [Google Android example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/gcm.rb)
41
+ 3. [Windows Phone example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/mpns.rb)
41
42
 
42
43
  ## Pending tasks
43
44
 
@@ -49,6 +50,9 @@ Feel free to contribute!!
49
50
  * Validate GCM notifications format and max size
50
51
  * Split GCM notifications in parts if more than 1000 destinations are given (currently raising exception)
51
52
  * Integrate with APNS Feedback service
53
+ * Validate MPNS notifications format
54
+ * Validate MPNS data
55
+ * Add other platforms (Amazon Fire...)
52
56
 
53
57
  ## Contributing
54
58
 
data/examples/mpns.rb ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'ruby-push-notifications'
6
+
7
+ device_urls = [
8
+ 'First device url here',
9
+ 'Second device url here'
10
+ ]
11
+
12
+ # Notification with toast type
13
+ notification = RubyPushNotifications::MPNS::MPNSNotification.new device_urls, { title: 'Title', message: 'Hello MPNS World!', type: :toast }
14
+
15
+ pusher = RubyPushNotifications::MPNS::MPNSPusher.new
16
+ pusher.push [notification]
17
+ p notification.results
@@ -0,0 +1,82 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+
4
+ module RubyPushNotifications
5
+ module MPNS
6
+ # Encapsulates a connection to the MPNS service
7
+ # Responsible for final connection with the service.
8
+ #
9
+ class MPNSConnection
10
+
11
+ # @private Content-Type HTTP Header string
12
+ CONTENT_TYPE_HEADER = 'Content-Type'
13
+
14
+ # @private text/xml content type
15
+ X_NOTIFICATION_CLASS = 'X-NotificationClass'
16
+
17
+ # @private Windows Phone Target
18
+ X_WINDOWSPHONE_TARGET = 'X-WindowsPhone-Target'
19
+
20
+ # @private text/xml content type
21
+ XML_CONTENT_TYPE = 'text/xml'
22
+
23
+ # @private Enumators for notification types
24
+ BASEBATCH = { tile: 1, toast: 2, raw: 3 }
25
+
26
+ # @private Enumators for delay
27
+ BATCHADDS = { delay450: 10, delay900: 20 }
28
+
29
+ # @private Windows Phone Target Types
30
+ WP_TARGETS = { toast: 'toast', tile: 'token' }
31
+
32
+ # Issues a POST request to the MPNS send endpoint to
33
+ # submit the given notifications.
34
+ #
35
+ # @param n [MPNSNotification]. The notification object to POST
36
+ # @param optional cert [String]. Contents of the PEM encoded certificate
37
+ # @return [Array]. The response of post
38
+ # (http://msdn.microsoft.com/pt-br/library/windows/apps/ff941099)
39
+ def self.post(n, cert = nil)
40
+ headers = build_headers(n.data[:type], n.data[:delay])
41
+ body = n.as_mpns_xml
42
+ responses = []
43
+ n.each_device do |url|
44
+ http = Net::HTTP.new url.host, url.port
45
+ if cert && url.scheme == 'https'
46
+ http.use_ssl = true
47
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
48
+ http.ca_file = cert
49
+ end
50
+ response = http.post url.path, body, headers
51
+ responses << { device_url: url.to_s, headers: extract_headers(response), code: response.code.to_i }
52
+ end
53
+ MPNSResponse.new responses
54
+ end
55
+
56
+ # Build Header based on type and delay
57
+ #
58
+ # @param type [Symbol]. The type of notification
59
+ # @param delay [Symbol]. The delay to be used
60
+ # @return [Hash]. Correct delay based on notification type
61
+ def self.build_headers(type, delay)
62
+ headers = {
63
+ CONTENT_TYPE_HEADER => XML_CONTENT_TYPE,
64
+ X_NOTIFICATION_CLASS => "#{(BASEBATCH[type] + (BATCHADDS[delay] || 0))}"
65
+ }
66
+ headers[X_WINDOWSPHONE_TARGET] = WP_TARGETS[type] unless type == :raw
67
+ headers
68
+ end
69
+
70
+ # Extract headers from response
71
+ # @param response [Net::HTTPResponse]. HTTP response for request
72
+ #
73
+ # @return [Hash]. Hash with headers with case-insensitive keys and string values
74
+ def self.extract_headers(response)
75
+ headers = {}
76
+ response.each_header { |k, v| headers[k] = v }
77
+ headers
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,96 @@
1
+ require 'builder'
2
+
3
+ module RubyPushNotifications
4
+ module MPNS
5
+ # Encapsulates a MPNS Notification.
6
+ # Actually support for raw, toast, tiles notifications
7
+ # (http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202945)
8
+ #
9
+ class MPNSNotification
10
+
11
+ # @return [MPNSResponse]. MPNSResponse with the results from sending this notification.
12
+ attr_accessor :results
13
+
14
+ # @return [Hash]. Payload to send.
15
+ # Toast :title => a bold message
16
+ # :message => the small message
17
+ # :param => a string parameter that is passed to the app
18
+ # Tile :image => a new image for the tile
19
+ # :count => a number to show on the tile
20
+ # :title => the new title of the tile
21
+ # :back_image => an image for the back of the tile
22
+ # :back_title => a title on the back of the tile
23
+ # :back_content => some content (text) for the back
24
+ # Raw :message => the full Hash message body
25
+ attr_reader :data
26
+
27
+ # @return [Array]. Array with the receiver's MPNS device URLs.
28
+ attr_reader :device_urls
29
+
30
+ # Initializes the notification
31
+ #
32
+ # @param [Array]. Array with the receiver's device urls.
33
+ # @param [Hash]. Payload to send.
34
+ # Toast :title => a bold message
35
+ # :message => the small message
36
+ # :param => a string parameter that is passed to the app
37
+ # Tile :image => a new image for the tile
38
+ # :count => a number to show on the tile
39
+ # :title => the new title of the tile
40
+ # :back_image => an image for the back of the tile
41
+ # :back_title => a title on the back of the tile
42
+ # :back_content => some content (text) for the back
43
+ # Raw :message => the full XML message body
44
+ def initialize(device_urls, data)
45
+ @device_urls = device_urls
46
+ @data = data
47
+ @data[:type] ||= :raw
48
+ end
49
+
50
+
51
+ # @return [String]. The GCM's XML format for the payload to send.
52
+ # (http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202945)
53
+ def as_mpns_xml
54
+ xml = Builder::XmlMarkup.new
55
+ xml.instruct!
56
+ if data[:type] != :raw
57
+ xml.tag!('wp:Notification', 'xmlns:wp' => 'WPNotification') do
58
+ case data[:type]
59
+ when :toast
60
+ xml.tag!('wp:Toast') do
61
+ xml.tag!('wp:Text1') { xml.text!(data[:title]) }
62
+ xml.tag!('wp:Text2') { xml.text!(data[:message]) }
63
+ xml.tag!('wp:Param') { xml.text!(data[:param]) } if data[:param]
64
+ end
65
+ when :tile
66
+ xml.tag!('wp:Tile') do
67
+ xml.tag!('wp:BackgroundImage') { xml.text!(data[:image]) } if data[:image]
68
+ xml.tag!('wp:Count') { xml.text!(data[:count].to_s) } if data[:count]
69
+ xml.tag!('wp:Title') { xml.text!(data[:title]) } if data[:title]
70
+ xml.tag!('wp:BackBackgroundImage') { xml.text!(data[:back_image]) } if data[:back_image]
71
+ xml.tag!('wp:BackTitle') { xml.text!(data[:back_title]) } if data[:back_title]
72
+ xml.tag!('wp:BackContent') { xml.text!(data[:back_content]) } if data[:back_content]
73
+ end
74
+ end
75
+ end
76
+ else
77
+ xml.root { build_hash(xml, data[:message]) }
78
+ end
79
+ xml.target!
80
+ end
81
+
82
+ def each_device
83
+ @device_urls.each do |url|
84
+ yield(URI.parse url)
85
+ end
86
+ end
87
+
88
+ def build_hash(xml, options)
89
+ options.each do |k, v|
90
+ xml.tag!(k.to_s) { v.is_a?(Hash) ? build_hash(xml, v) : xml.text!(v.to_s) }
91
+ end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,29 @@
1
+
2
+ module RubyPushNotifications
3
+ module MPNS
4
+
5
+ # This class is responsible for sending notifications to the MPNS service.
6
+ #
7
+ class MPNSPusher
8
+
9
+ # Initializes the MPNSPusher
10
+ #
11
+ # @param certificate [String]. The PEM encoded MPNS certificate.
12
+ # (http://msdn.microsoft.com/pt-br/library/windows/apps/ff941099)
13
+ def initialize(certificate = nil)
14
+ @certificate = certificate
15
+ end
16
+
17
+ # Actually pushes the given notifications.
18
+ # Assigns every notification an array with the result of each
19
+ # individual notification.
20
+ #
21
+ # @param notifications [Array]. Array of MPNSNotification to send.
22
+ def push(notifications)
23
+ notifications.each do |notif|
24
+ notif.results = MPNSConnection.post notif, @certificate
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,81 @@
1
+
2
+ module RubyPushNotifications
3
+ module MPNS
4
+
5
+ # This class encapsulates a response received from the MPNS service
6
+ # and helps parsing and understanding the received messages/codes.
7
+ #
8
+ class MPNSResponse
9
+
10
+ # @return [Integer] the number of successfully sent notifications
11
+ attr_reader :success
12
+
13
+ # @return [Integer] the number of failed notifications
14
+ attr_reader :failed
15
+
16
+ # @return [Array] Array of a MPNSResult for every receiver of the notification
17
+ # sent indicating the result of the operation.
18
+ attr_reader :results
19
+
20
+ # Initializes the MPNSResponse and runs response parsing
21
+ #
22
+ # @param responses [Array]. Array with device_urls and http responses
23
+ def initialize(responses)
24
+ parse_response responses
25
+ end
26
+
27
+ def ==(other)
28
+ (other.is_a?(MPNSResponse) &&
29
+ success == other.success &&
30
+ failed == other.failed &&
31
+ results == other.results) || super(other)
32
+ end
33
+
34
+ private
35
+
36
+ # Parses the response extracting counts for successful, failed messages.
37
+ # Also creates the results array assigning a MPNSResult subclass for each
38
+ # device URL the notification was sent to.
39
+ #
40
+ # @param responses [Array]. Array of hash responses
41
+ def parse_response(responses)
42
+ @success = responses.count { |response| response[:code] == 200 }
43
+ @failed = responses.count { |response| response[:code] != 200 }
44
+ @results = responses.map do |response|
45
+ mpns_result_for response[:code],
46
+ response[:device_url],
47
+ response[:headers]
48
+ end
49
+ end
50
+
51
+ # Factory method that, for each MPNS result object assigns a MPNSResult
52
+ # subclass.
53
+ #
54
+ # @param code [Integer]. The HTTP status code received
55
+ # @param device_url [String]. The receiver's MPNS device url.
56
+ # @param headers [Hash]. The HTTP headers received.
57
+ # @return [MPNSResult]. Corresponding MPNSResult subclass
58
+ def mpns_result_for(code, device_url, headers)
59
+ case code
60
+ when 200
61
+ MPNSResultOK.new device_url, headers
62
+ when 400
63
+ MalformedMPNSResultError.new device_url
64
+ when 401
65
+ MPNSAuthError.new device_url
66
+ when 404
67
+ MPNSInvalidError.new device_url, headers
68
+ when 406
69
+ MPNSLimitError.new device_url, headers
70
+ when 412
71
+ MPNSPreConditionError.new device_url, headers
72
+ when 500..599
73
+ MPNSInternalError.new device_url
74
+ else
75
+ MPNSResultError.new device_url
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,182 @@
1
+
2
+ module RubyPushNotifications
3
+ module MPNS
4
+ # Class that encapsulates the result of a single sent notification to a single
5
+ # Device URL
6
+ # (https://msdn.microsoft.com/en-us/library/windows/apps/ff941100%28v=vs.105%29.aspx)
7
+ class MPNSResult
8
+ # @return [String]. Receiver MPNS device URL.
9
+ attr_accessor :device_url
10
+
11
+ # @private X-NotificationStatus HTTP Header string
12
+ X_NOTIFICATION_STATUS = 'x-notificationstatus'
13
+
14
+ # @private X-DeviceConnectionStatus HTTP Header string
15
+ X_DEVICE_CONNECTION_STATUS = 'x-deviceconnectionstatus'
16
+
17
+ # @private X-SubscriptionStatus HTTP Header string
18
+ X_SUBSCRIPTION_STATUS = 'x-subscriptionstatus'
19
+
20
+ end
21
+
22
+ # Indicates that the notification was successfully sent to the corresponding
23
+ # device URL
24
+ class MPNSResultOK < MPNSResult
25
+ # @return [String]. The status of the notification received
26
+ # by the Microsoft Push Notification Service.
27
+ attr_accessor :notification_status
28
+
29
+ # @return [String]. The connection status of the device.
30
+ attr_accessor :device_connection_status
31
+
32
+ # @return [String]. The subscription status.
33
+ attr_accessor :subscription_status
34
+
35
+ def initialize(device_url, headers = nil)
36
+ @device_url = device_url
37
+ @notification_status = headers[X_NOTIFICATION_STATUS]
38
+ @device_connection_status = headers[X_DEVICE_CONNECTION_STATUS]
39
+ @subscription_status = headers[X_SUBSCRIPTION_STATUS]
40
+ end
41
+
42
+ def ==(other)
43
+ (other.is_a?(MPNSResultOK) &&
44
+ device_url == other.device_url &&
45
+ notification_status == other.notification_status &&
46
+ device_connection_status == other.device_connection_status &&
47
+ subscription_status == other.subscription_status) || super(other)
48
+ end
49
+ end
50
+
51
+ # This error occurs when the cloud service sends a notification
52
+ # request with a bad XML document or malformed notification URI.
53
+ class MalformedMPNSResultError < MPNSResult
54
+ def initialize(device_url)
55
+ @device_url = device_url
56
+ end
57
+
58
+ def ==(other)
59
+ (other.is_a?(MalformedMPNSResultError) &&
60
+ device_url == other.device_url) || super(other)
61
+ end
62
+ end
63
+
64
+ # Sending this notification is unauthorized.
65
+ class MPNSAuthError < MPNSResult
66
+ def initialize(device_url)
67
+ @device_url = device_url
68
+ end
69
+
70
+ def ==(other)
71
+ (other.is_a?(MPNSAuthError) &&
72
+ device_url == other.device_url) || super(other)
73
+ end
74
+ end
75
+
76
+ # The subscription is invalid and is not present on the Push Notification Service.
77
+ class MPNSInvalidError < MPNSResult
78
+ # @return [String]. The status of the notification received
79
+ # by the Microsoft Push Notification Service.
80
+ attr_accessor :notification_status
81
+
82
+ # @return [String]. The connection status of the device.
83
+ attr_accessor :device_connection_status
84
+
85
+ # @return [String]. The subscription status.
86
+ attr_accessor :subscription_status
87
+
88
+ def initialize(device_url, headers)
89
+ @device_url = device_url
90
+ @notification_status = headers[X_NOTIFICATION_STATUS]
91
+ @device_connection_status = headers[X_DEVICE_CONNECTION_STATUS]
92
+ @subscription_status = headers[X_SUBSCRIPTION_STATUS]
93
+ end
94
+
95
+ def ==(other)
96
+ (other.is_a?(MPNSInvalidError) &&
97
+ device_url == other.device_url &&
98
+ notification_status == other.notification_status &&
99
+ device_connection_status == other.device_connection_status &&
100
+ subscription_status == other.subscription_status) || super(other)
101
+ end
102
+ end
103
+
104
+ # This error occurs when an unauthenticated cloud service has reached
105
+ # the per-day throttling limit for a subscription,
106
+ # or when a cloud service (authenticated or unauthenticated)
107
+ # has sent too many notifications per second.
108
+ class MPNSLimitError < MPNSResult
109
+ # @return [String]. The status of the notification received
110
+ # by the Microsoft Push Notification Service.
111
+ attr_accessor :notification_status
112
+
113
+ # @return [String]. The connection status of the device.
114
+ attr_accessor :device_connection_status
115
+
116
+ # @return [String]. The subscription status.
117
+ attr_accessor :subscription_status
118
+
119
+ def initialize(device_url, headers)
120
+ @device_url = device_url
121
+ @notification_status = headers[X_NOTIFICATION_STATUS]
122
+ @device_connection_status = headers[X_DEVICE_CONNECTION_STATUS]
123
+ @subscription_status = headers[X_SUBSCRIPTION_STATUS]
124
+ end
125
+
126
+ def ==(other)
127
+ (other.is_a?(MPNSLimitError) &&
128
+ device_url == other.device_url &&
129
+ notification_status == other.notification_status &&
130
+ device_connection_status == other.device_connection_status &&
131
+ subscription_status == other.subscription_status) || super(other)
132
+ end
133
+ end
134
+
135
+ # The device is in a disconnected state.
136
+ class MPNSPreConditionError < MPNSResult
137
+ # @return [String]. The status of the notification received
138
+ # by the Microsoft Push Notification Service.
139
+ attr_accessor :notification_status
140
+
141
+ # @return [String]. The connection status of the device.
142
+ attr_accessor :device_connection_status
143
+
144
+ def initialize(device_url, headers)
145
+ @device_url = device_url
146
+ @notification_status = headers[X_NOTIFICATION_STATUS]
147
+ @device_connection_status = headers[X_DEVICE_CONNECTION_STATUS]
148
+ end
149
+
150
+ def ==(other)
151
+ (other.is_a?(MPNSPreConditionError) &&
152
+ device_url == other.device_url &&
153
+ notification_status == other.notification_status &&
154
+ device_connection_status == other.device_connection_status) || super(other)
155
+ end
156
+ end
157
+
158
+ # The Push Notification Service is unable to process the request.
159
+ class MPNSInternalError < MPNSResult
160
+ def initialize(device_url)
161
+ @device_url = device_url
162
+ end
163
+
164
+ def ==(other)
165
+ (other.is_a?(MPNSInternalError) &&
166
+ device_url == other.device_url) || super(other)
167
+ end
168
+ end
169
+
170
+ # Unknow Error
171
+ class MPNSResultError < MPNSResult
172
+ def initialize(device_url)
173
+ @device_url = device_url
174
+ end
175
+
176
+ def ==(other)
177
+ (other.is_a?(MPNSResultError) &&
178
+ device_url == other.device_url) || super(other)
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,5 @@
1
+ require 'ruby-push-notifications/mpns/mpns_connection'
2
+ require 'ruby-push-notifications/mpns/mpns_notification'
3
+ require 'ruby-push-notifications/mpns/mpns_pusher'
4
+ require 'ruby-push-notifications/mpns/mpns_response'
5
+ require 'ruby-push-notifications/mpns/mpns_result'
@@ -1,3 +1,3 @@
1
1
  module RubyPushNotifications
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,3 +1,4 @@
1
1
  require 'ruby-push-notifications/version'
2
2
  require 'ruby-push-notifications/apns'
3
3
  require 'ruby-push-notifications/gcm'
4
+ require 'ruby-push-notifications/mpns'
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
  spec.require_paths = ['lib']
22
22
 
23
+ spec.add_dependency 'builder', '~> 3.2'
24
+
23
25
  spec.add_development_dependency 'bundler', '~> 1.6'
24
26
  spec.add_development_dependency 'rake', '~> 10.4'
25
27
  spec.add_development_dependency 'rspec', '~> 3.2'
@@ -12,4 +12,12 @@ FactoryGirl.define do
12
12
 
13
13
  initialize_with { new registration_ids, data }
14
14
  end
15
+
16
+ factory :mpns_notification, class: 'RubyPushNotifications::MPNS::MPNSNotification' do
17
+ device_urls { [generate(:mpns_device_url)] }
18
+ data message: { value1: 'hello' }
19
+
20
+ initialize_with { new device_urls, data }
21
+ end
22
+
15
23
  end
data/spec/factories.rb CHANGED
@@ -7,4 +7,9 @@ FactoryGirl.define do
7
7
  sequence :gcm_registration_id do |i|
8
8
  "APA91bHPRgkF3JUikC4ENAHEeMrd41Zxv3hVZjC9KtT8OvPVGJ-hQMRKRrZuJAEcl7B338qju59zJMjw2DELjzEvxwYv7hH5Ynpc1ODQ0aT4U4OFEeco8ohsN5PjL1iC2dNtk2BAokeMCg2ZXKqpc8FXKmhX94kIxQ#{i}"
9
9
  end
10
+
11
+ sequence :mpns_device_url do |i|
12
+ "http://s.notify.live.net/u/1/bn1/HmQAAACP-0esPuxBSkzBNNXH4W0lV3lK-stEw6eRfpXX39uYbM7IwehXOTO9pRBjaaGECWOdD_7x5j5U4w4iXG4hGxer/d2luZG93c3Bob25lZGVmYXVsdA/EMDhx32Q5BG0DWnZpuVX1g/kRFAu0-jnhMQ-HG94rXzrbb0wQk#{i}"
13
+ end
14
+
10
15
  end
@@ -0,0 +1,46 @@
1
+
2
+ module RubyPushNotifications
3
+ module MPNS
4
+ describe MPNSConnection do
5
+
6
+ describe '::post' do
7
+
8
+ let(:body) { 'abc' }
9
+ let(:headers) {
10
+ {
11
+ 'x-notificationstatus' => 'Received',
12
+ 'x-deviceconnectionstatus' => 'Connected',
13
+ 'x-subscriptionstatus' => 'Active'
14
+ }
15
+ }
16
+ let(:device_urls) { [generate(:mpns_device_url)] }
17
+ let(:toast_data) { { title: 'Title', message: 'Hello MPNS World!', type: :toast } }
18
+ let(:toast_notification) { build :mpns_notification, device_urls: device_urls, data: toast_data }
19
+
20
+ before do
21
+ stub_request(:post, %r{s.notify.live.net}).
22
+ to_return status: [200, 'OK'], body: body, headers: headers
23
+ end
24
+
25
+ it 'runs the right request' do
26
+ MPNSConnection.post toast_notification
27
+
28
+ expect(WebMock).
29
+ to have_requested(:post, toast_notification.device_urls[0]).
30
+ with(body: toast_notification.as_mpns_xml, headers: { 'Content-Type' => 'text/xml', 'X-NotificationClass' => '2', 'X-WindowsPhone-Target' => :toast}).
31
+ once
32
+ end
33
+
34
+ it 'returns the response encapsulated in a Hash object' do
35
+ responses = [
36
+ { device_url: toast_notification.device_urls[0],
37
+ headers: headers,
38
+ code: 200
39
+ }
40
+ ]
41
+ expect(MPNSConnection.post toast_notification).to eq MPNSResponse.new(responses)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end