ruby-push-notifications 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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